스트림 API 컬렉터

스트림이 중간 연산을 거쳐 최종 연산으로써 데이터를 처리할 때, 그 결과물이 필요한 경우가 많다. 이 최종 연산에 Collectors를 활용한다.

collect 연산은 반환값을 만들어내는 최종 연산이다. Collectors 클래스 안에 준비된 여러 메서드를 통해서 다양한 수집 방식을 적용할 수 있다. 필요한 대부분의 기능이 Collectors에 이미 구현되어 있기 때문에, Collector 인터페이스를 직접 구현하는 것보다는 Collectors의 사용법을 익히는 것이 중요하다.

기본적인 수집

import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

public class Collectors1Basic {
    public static void main(String[] args) {
        //기본 기능
        List<String> list = Stream.of("Java", "Spring", "JPA").collect(toList());

        //수정 불가능 리스트
        List<Integer> unmodifiableList = Stream.of(1, 2, 3).collect(toUnmodifiableList());
//        unmodifiableList.add(4); //예외 발생

        Set<Integer> set = Stream.of(1, 2, 2, 3, 3, 3).collect(toSet());
        Set<Integer> unmodifiableSet = Stream.of(1, 2, 2, 3, 3, 3).collect(toUnmodifiableSet());

        //타입 지정
        TreeSet<Integer> treeSet = Stream.of(3, 4, 5, 2, 1).collect(toCollection(TreeSet::new));
    }
}

Collectors 클래스에는 스트림을 다양한 컬렉션으로 수집할 수 있는 API들이 정의되어 있다. 원하는 컬렉션 구현체를 직접 지정할 수도 있으며, static import를 사용할 수 있다.

Map 수집

그룹과 분할 수집

최솟값, 최댓값 수집

  • Collectors.maxBy()Collectors.minBy()를 통해 최소, 최댓값을 구할 수 있다.

  • 다만 스트림 자체가 제공하는 max(), min() 메서드를 쓰면 더 간단하다.

  • 기본형 특화 스트림을 쓰면 max()처럼 바로 결과를 얻을 수 있다.

  • Collectors의 일부 기능은 스트림에서 직접 제공하는 기능과 중복되며, 다운 스트림 컬렉터에서 유용하게 사용할 수 있다.

통계 수집

Collectors의 일부 기능은 스트림에서 직접 제공하는 기능과 중복되며, 다운 스트림 컬렉터에서 유용하게 사용할 수 있다.

리듀싱 수집

  • Collectors.reducing()은 최종적으로 하나의 값으로 요소들을 합치는 방식을 지정한다. 스트림 자체의 reduce() 메서드와 유사한 기능이다.

  • 문자열을 이어붙일 때는 Collectors.joining() 또는 String.join()을 쓰는 게 더 간편하다.

  • Collectors의 일부 기능은 스트림에서 직접 제공하는 기능과 중복되며, 다운 스트림 컬렉터에서 유용하게 사용할 수 있다.


다운 스트림 컬렉터

다운 스트림 컬렉터란?

  • Collectors.groupingBy() 또는 Collectors.partitioningBy()에서 두번째 인자로 전달되는 Collector를 가리켜 다운 스트림 컬렉터라 한다.

  • 예를 들어 Collectors.groupingBy(classifier, downstreamCollector) 형태로 사용될 때, downstreamCollectorclassifier에 의해 분류된 각 그룹 내부의 요소들을 다시 한번 어떻게 처리할지를 정의하는 역할을 한다.

  • 만약 다운 스트림 컬렉터를 명시하지 않으면 기본적으로 Collectors.toList()가 적용되어 그룹별 요소들을 List로 모은다.

  • 그룹별 개수를 세거나, 평균을 구하거나, 특정 필드를 뽑아서 맵핑하는 등의 작업이 필요하다면 적절한 다운 스트림 컬렉터를 추가로 지정해야 한다.

  • 정리하면 다운 스트림 컬렉터는 그룹화(또는 분할)를 먼저 한 뒤, 각 그룹(또는 파티션) 내부의 요소들을 어떻게 처리할 것인가를 지정하는 데 사용된다.

다운 스트림 컬렉터가 필요한 이유

  • groupingBy()를 사용하면 일단 요소가 그룹별로 묶이지만, 그룹 내 요소를 어떻게 처리할지는 기본적으로 toList()만 적용된다.

  • 하지만 보통 "그룹별 총합, 평균, 최대, 최소, 매핑된 결과, 통계" 등을 바로 얻고 싶을 때가 많다.

  • 다운 스트림 컬렉터는 그룹화된 이후 그룹 내부에서 추가적인 연산 또는 결과물을 정의하는 역할을 한다.

  • 즉 다운 스트림 컬렉터를 사용하면 그룹 내부를 다시 한번 모으거나 집계하여 원하는 결과를 얻을 수 있다.

다음은 Student 클래스를 각 학년별로 그룹화한 다음, 그룹화한 학년별 점수의 합을 구하는 예시이다.

img_2.png

다운 스트림 컬렉터 예제 - 1

다운 스트림 컬렉터 - toList()

img_3.png

다운 스트림 컬렉터 - mapping()

img_4.png

다운 스트림 컬렉터 - 집계

img_5.png

다운 스트림 컬렉터 예제 - 2

다운 스트림 컬렉터 - reduce()

img_6.png

👆 mapping() vs collectingAndThen()

  • mapping() : 그룹화(또는 분할)된 각 그룹 내의 개별 요소들을 다른 값으로 변환한 뒤, 그 변환된 값들을 다시 다른 Collector로 수집할 수 있게 해준다.

    • 주된 목적 : 그룹 내 개별 요소를 변환한 뒤, 해당 변환 결과를 다른 Collector로 수집

    • 처리 방식 : 그룹화각 요소를 변환(mapping)List나 Set 등으로 수집

  • collectingAndThen() : 다운 스트림 컬렉터가 최종 결과를 만든 뒤한 번 더 후처리할 수 있도록 해준다. 즉 1차 Collector → 후처리 함수 순서로 작업한다.

    • 주된 목적 : 그룹 내 요소들을 이미 한번 수집한 결과를 추가 가공하거나 최종 타입으로 변환

    • 처리 방식 : 그룹화최댓값/최솟값/합계 등 수집(collecting)결과를 후처리(AndThen)

mapping()그룹화된 요소 하나하나를 변환하는 데 유용하고, collectingAndThen()은 이미 만들어진 전체 그룹의 결과를 최종 한번 더 작업하는 데 사용한다.

Last updated