스트림 API

스트림 API (The Stream API)

메모리에 담긴 데이터를 반복문 대신 선언적으로 처리하고 싶을 때 쓰는 도구가 바로 스트림 API예요. '무엇을 할지'만 적으면 map/filter/reduce 방식으로 데이터 변환과 집계가 파이프라인처럼 흘러가죠. dev.java의 스트림 시리즈는 메모리 데이터 처리의 기본 연산부터 중간 연산, 리듀싱, 수집기(Collector), 병렬 스트림, Gatherer API까지 폭넓게 다루고 있어요.

출처: The Stream API — Dev.java

메모리 데이터를 map/filter/reduce로 처리하기

스트림의 핵심은 데이터를 map(변환)filter(선별)reduce(집계) 의 세 연산으로 다루는 거예요. 컬렉션에서 stream()을 얻고, 변환이나 필터링 같은 중간 연산을 이어 붙인 뒤, 마지막에 결과를 모으는 최종 연산으로 마무리하는 구조죠.

List<String> names = users.stream()
    .filter(u -> u.age() >= 20)        // 중간 연산: 선별
    .map(User::name)                    // 중간 연산: 변환
    .toList();                          // 최종 연산: 결과 수집

스트림 생성과 중간·최종 연산

스트림을 만드는 패턴은 다양해요. 컬렉션에서 stream(), 배열이나 값들에서 Stream.of(...), 파일·난수 같은 소스에서도 만들 수 있죠. map·filter 외에도 flatMap, sorted, distinct, limit 같은 중간 연산이 있고, 파이프라인을 끝내는 최종 연산에는 toList, count, forEach, reduce 등이 있어요. 중간 연산은 게으르게(lazy) 동작하고 최종 연산이 호출될 때 실제로 실행되는 점이 포인트예요.

리듀싱과 수집기 (Reduce & Collector)

스트림을 하나의 값으로 축약하는 걸 리듀싱이라 하고, 이진 연산자로 요소를 결합하는 reduce가 대표적이에요. 반면 여러 결과를 가변 컨테이너(가령 리스트·맵)에 모으는 건 Collectors 팩토리 클래스의 수집기들이 담당해요. 기본 수집기만으로 부족하면 Collector 인터페이스를 직접 구현해 나만의 수집기를 만들 수도 있습니다.

Optionals와 병렬 스트림

항상 값을 만들 수 없는 메서드를 모델링할 때는 Optional 클래스를 써요. 값이 있음/없음을 명시적으로 다뤄 NPE(NullPointerException)를 줄이는 데 도움을 주죠. 그리고 병렬 스트림(parallel streams) 을 쓰면 내부적으로 여러 스레드가 나눠 처리해 큰 데이터를 더 빠르게 다룰 수 있어요. 다만 병렬화는 항상 빠른 게 아니니, 데이터 크기와 작업 성격을 고려해야 합니다.

더 알아보기