[Java] 스트림(Stream)에 대하여

2025. 10. 14. 14:27·Java

스트림 (Stream) 

자바 스트림(Stream)은 Java 8에서 도입된 기능으로, 컬렉션이나 배열 등의 데이터를 함수형 방식으로 처리할 수 있게 해주는 핵심 기능이다. '데이터 소스'에서 추출된 요소들이 순차적으로 흐르는 파이프라인을 의미한다. 코드를 간결하고 가독성 있게 만들어주며 병렬 처리까지 쉽게 해준다. 

 

특징 

1) 데이터 소스 : 배열, 컬렉션 등에서 얻는다. 

2) 함수형 : 익명 함수 (람다)를 사용하여 코드를 간결하게 만든다. 

3) 비파괴적 : 원본 데이터를 변경하지 않는다. 

4) 단 한 번만 사용 : 스트림은 최종 연산 후 닫히며 재사용할 수 없다. 

 

왜 스트림을 사용하는가 ? 

  • 전통적인 방식 : 외부 반복으로 개발자가 직접 인덱스를 관리하고 반복을 제어해야 한다. 
  • 스트림 방식 : 내부 반복으로 데이터 처리에 대한 로직만 제공하면, 스트림이 알아서 반복을 처리하고 최적화한다. 

스트림 파이프라인의 3단계 작동 원리 

스트림을 이용한 데이터 처리는 항상 생성 -> 중간 연산 -> 최종 연산의 3 단계를 거치는 파이프라인 구조를 가진다. 

 

1) 1단계 : 스트림 생성

데이터 소스를 스트림으로 만든다.

메서드 설명 예시
Collection.stream() List, Set 등 컬렉션에서 스트림 생성 list.stream()
Arrays.stream() 배열에서 스트림 생성 Arrays.sream(arr) 
Stream.of() 고정된 값(리터럴)들로 스트림 생성 Stream.of("A", "B", "C")
IntStream.range() 기본 타입 범위 스트림 생성 IntStream.range(1,10)

 

2) 2단계 : 중간 연산 

데이터를 필터링, 가공, 변형하는 단계이다. 중간 연산은 여러 개를 연결할 수 있으며, 연산 결과로 다시 스트림을 반환한다. 

메서드 역할 설명
filter(Predicate) 조건에 맞는 요소만 걸러냄 filter(n -> n > 10) 
map(Function) 요소를 원하는 형태로 변환 map(s -> s.length())
sorted() 요소들을 정렬 sorted() 또는 sorted(Comparator)
distinct() 중복 요소 제거  
limit(n) 앞에서부터 n개만 잘라냄   

중간 연산은 실제 데이터를 처리하지 않는다. 최종 연산이 호출될 때까지 대기한다. 

 

3) 3단계 : 최종 연산 

구축된 파이프라인을 실행시키고, 결과를 반환하거나 스트림을 소비한다. 이 연산이 호출되는 순간에만 중간 연산들이 모두 실행된다.

메서드 역할 반환 타입
forEach(Consumer) 각 요소를 반복하여 작업 수행 void
collect(Collector) 스트림의 요소를 다시 컬렉션으로 모음 List, Set, Map 등
reduce() 모든 요소를 하나로 합쳐서 반환 Optional 또는 타입
count() 요소의 개수 반환 long
anyMatch() 조건에 맞는 요소가 하나라도 있는지 확인 boolean

예시 

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Employee {
    private String name;
    private int salary;
    private String department;

    public Employee(String name, int salary, String department) {
        this.name = name;
        this.salary = salary;
        this.department = department;
    }

    // Getter 메서드
    public String getName() { return name; }
    public int getSalary() { return salary; }
    public String getDepartment() { return department; }

    @Override
    public String toString() {
        return "Employee{" + "name='" + name + '\'' + ", salary=" + salary + ", dept='" + department + '\'' + '}';
    }
}

public class StreamUsageExample {

    public static void main(String[] args) {
        // 데이터 소스 생성
        List<Employee> allEmployees = Arrays.asList(
            new Employee("김철수", 6000, "개발부"),
            new Employee("이영희", 4500, "영업부"),
            new Employee("박민수", 7500, "개발부"),
            new Employee("최지영", 4000, "마케팅부"),
            new Employee("김하나", 5500, "영업부")
        );

        // 1. 필터링 (filtering)
        // 급여가 5000만 원 이상인 직원만 추출
        List<Employee> highPaidList = allEmployees.stream()
            .filter(emp -> emp.getSalary() >= 5000)
            .collect(Collectors.toList());
        
        System.out.println("고액 연봉자: " + highPaidList);
        // 결과: [Employee{name='김철수', salary=6000, ...}, Employee{name='박민수', salary=7500, ...}, Employee{name='김하나', salary=5500, ...}]


        // 2. 데이터 가공 및 변환 
        // 직원 목록에서 이름만 추출
        List<String> names = allEmployees.stream()
            .map(Employee::getName) // Employee -> String 변환
            .collect(Collectors.toList());
        
        System.out.println("직원 이름 목록: " + names);
        // 결과: [김철수, 이영희, 박민수, 최지영, 김하나]

	
    	// 3. 데이터 집계 
        // 영업부 직원들의 급여 총합 계산
        long salesTotalSalary = allEmployees.stream()
            .filter(emp -> "영업부".equals(emp.getDepartment())) // 1. 영업부 필터링
            .mapToLong(Employee::getSalary) // 2. 급여를 long 스트림으로 변환
            .sum(); // 3. 최종 연산: 합계
            
        System.out.println("영업부 급여 총합: " + salesTotalSalary + "만 원");
        // 결과: 4500 + 5500 = 10000만 원


		// 4. 조건 검사 
        // '마케팅부'에 속한 직원이 한 명이라도 있는지 확인
        boolean isMarketingPresent = allEmployees.stream()
            .anyMatch(emp -> "마케팅부".equals(emp.getDepartment())); 
            
        System.out.println("마케팅부 존재 여부: " + isMarketingPresent);
        // 결과: true (찾는 즉시 스트림 종료)


		// 5. 데이터 그룹화 
        // 부서별로 직원 목록을 그룹화
        Map<String, List<Employee>> employeesByDept = allEmployees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));
            
        System.out.println("부서별 그룹: " + employeesByDept);
        // 결과: {개발부=[김철수, 박민수], 영업부=[이영희, 김하나], 마케팅부=[최지영]}
    }
}

'Java' 카테고리의 다른 글

[Java] 자바 변수 4가지 정리 (전역, 지역, 정적, 멤버 변수)  (0) 2025.11.10
[Java] 원시타입(Primitive Type)과 참조타입(Reference Type)  (0) 2025.10.14
[Java] 실수를 나타내는 Double, double, float, decimal 의 차이  (0) 2025.10.13
[Java] POJO(Plain Old Java Object)  (0) 2025.09.16
[Java] 객체지향설계의 5가지 원칙 (SOLID 원칙)  (2) 2025.08.03
'Java' 카테고리의 다른 글
  • [Java] 자바 변수 4가지 정리 (전역, 지역, 정적, 멤버 변수)
  • [Java] 원시타입(Primitive Type)과 참조타입(Reference Type)
  • [Java] 실수를 나타내는 Double, double, float, decimal 의 차이
  • [Java] POJO(Plain Old Java Object)
erika0915
erika0915
백엔드 개발자가 되고 싶어요 .
  • erika0915
    erikoding
    erika0915
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • 프로젝트 (13)
        • 끼니콩 (3)
        • 덕메랑 (3)
        • handDoc (7)
        • Haeil (0)
      • Java (9)
        • 클린코더스 (0)
      • Spring (30)
      • Redis (3)
      • CS (7)
        • 운영체제 (3)
        • 컴퓨터구조 (0)
        • 네트워크 (4)
      • DevOps (2)
      • 코딩테스트 (0)
      • Tech (14)
        • TDD (1)
        • 정리 (5)
        • 우테코 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    promtail
    운영체제
    java
    Network
    AI
    레디스
    TDD
    도커
    redis
    git
    몽고디비
    네트워크
    OS
    Spring
    코드레빗
    coderabbit
    자바
    CoolSMS
    지라
    jira
    스프링부트
    docker
    파인튜닝
    깃
    MongoDB
    깃허브
    github
    스프링
    STT
    springboot
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
erika0915
[Java] 스트림(Stream)에 대하여

티스토리툴바