Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [2 - collect()] (JAVA)
안녕하세요? 코딩중독입니다.
저번 시간에는 수집을 제외한 최종 처리 메소드에 대해 알아 보았습니다. 오늘은 수집을 사용한 최종 처리 메소드를 설명하겠습니다.
시작하기 전에...
예제에서 사용할 사용자 정의 클래스를 미리 만들어 두겠습니다. 학생의 정보를 담는 클래스로, 아래 코드를 참고하시면 되겠습니다.
수집
스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect() 메소드를 제공하고 있습니다. 이 메소드를 이용하면 필요한 요소만 컬렉션으로 담을 수 있고, 요소들을 그룹핑한 후 집계할 수 있습니다.
필터링한 요소 수집
(1) 기본 컬렉션 (List, Set, ...)에 수집하기
스트림에서 collect() 메소드는 매개 변수에 따라 쓰임새가 달라지는데, 매개 변수를 Collector<T, A, R>을 취할 경우 주로 어떤 요소를 기본 컬렉션에 수집할 때 사용됩니다. Collector<T, A, R>에서 T는 요소이고, A는 누적기이며, R은 요소가 저장될 컬렉션입니다. 즉, T 요소를 A 누적기가 R에 저장한다는 의미죠.
아래는 Collectors 클래스의 정적 메소드입니다. 이를 이용하여 Collectior의 구현 객체를 얻어낼 수 있습니다.
리턴 타입 | Collectors의 정적 메소드 | 설명 |
Collector<T, ?, List<T>> | toList() | T를 List에 저장 |
Collector<T, ?, Set<T>> | toSet() | T를 Set에 저장 |
Collector<T, ?, Collection<T>> | toCollection(Supplier<Collection<T>>) | T를 Supplier가 제공한 Collection에 저장 |
Collector<T, ?, Map<K, U>> | toMap(Function<T, K> keyMapper, Function<T, U> valueMapper) | T를 K와 U로 매핑해서 K를 키로, U를 값으로 Map에 저장 |
Collector<T, ?, ConcurrentMap<K, U>> | toConcurrentMap(Function<T, K> keyMapper, Function<T, U> valueMapper> | T를 K와 U로 매핑해서 K를 키로, U를 값ㅎ으로 ConcurrentMap에 저장 |
리턴값인 Collector를 보면 A(누적기)가 ?로 되어 있는데, 이것은 Collector가 R(컬렉션)에 T(요소)를 저장하는 방법을 알고 있어서 A가 필요 없기 때문입니다. 또한, Map과 ConcurrentMap의 차이점은 쓰레드의 안정성입니다. Map이 쓰레드에 안전하지 않고, ConcurrentMap이 쓰레드에 안전하기때문에 멀티쓰레드환경에서는 ConcurrentMap을 쓰는 것을 권장합니다.
아래는 전체 학생 중에서 남학생만 필터링해서 별도의 List를 생성하는 코드입니다.
그리고 maleList에서 학생의 이름을 출력한다면, "이산", "진영", "별찬"이 출력될 것입니다.
같은 방식으로 Set 컬렉션도 얻어낼 수 있지만, HashSet 컬렉션을 얻고 싶다면 toCollection() 메소드를 작성해야합니다. 그리고 이 메소드의 매개변수로는 Supplier<Collection<T>>를 넘겨주면 됩니다.
아래는 전체 학생 중에서 남학생만 필터링해서 별도의 HashSet을 생성하는 코드입니다.
일부러 Set의 특징을 살리기 위하여 이름이 같은 학생을 넣었습니다. 그리고 HashSet 내에서 이러한 객체가 같은지 비교하려면 hashCode() 메소드와 equals() 메소드를 오버라이드 해 주어야합니다. 자세한 설명은 이곳을 참고하시길 바랍니다.
(2) 사용자 정의 컨테이너에 수집하기
List, Set, Map과 같은 컬렉션이 아니라 사용자 정의 컨테이너 객체에 요소를 수집하는 방법에 대해 알아봅시다. 위에서는 매개변수로 Collector을 전달하는 collect() 메소드를 사용하였지만, 이번에는 총 3개의 매개변수를 필요로하는 collect() 메소드를 사용해야 합니다.
인터페이스 | 리턴 타입 | 메소드(매개 변수) |
Stream | R | collect(Supplier<R>, BiConsumer<R, ?super T>, BiConsumer<R, R>) |
IntStream | R | collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R, R>) |
LongStream | R | collect(Supplier<R>, ObjLongConsumer<R>, BiConsumer<R, R>) |
DoubleStream | R | collect(Supplier<R>, ObjDouble<R>, BiConsumer<R, R>) |
collect의 매개 변수에 대해 자세히 설명하겠습니다.
첫 번째로, Supplier<T>는 요소들이 수집될 컨테이너 객체(R)을 생성하는 역할을 합니다. 싱글 쓰레드 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성합니다. 반면, 멀티 쓰레드 스트림에서는 여러 번 Supplier가 실행되고 쓰레드 별로 여러 개의 컨테이너 객체를 생성합니다. 그리고 최종적으로 하나의 컨테이너 객체로 결합됩니다.
두 번째로, XXXConsumer는 컨테이너 객체(R)에 요소(T)를 수집하는 역할을 합니다. 스트림에서 요소를 컨테이너에 수집할 때마다 XXXConsumer가 실행됩니다.
마지막으로, BiConsumer는 컨테이너 객체(R)을 결합하는 역할을 하는데, 싱글 쓰레드 스트림에서는 호출되지 않고, 병렬 처리 스트림에서만 호출되어 쓰레드 별로 생성된 컨테이너 객체를 결합해서 최종 컨테이너 객체를 완성합니다.
리턴 타입 R은 요소들이 최종 수집된 컨테이너 객체를 의미합니다. 싱글 쓰레드 스트림에서는 리턴 객체가 첫 번째 Supplier가 생성한 객체지만, 병렬 처리 스트림에서는 최종 결합된 컨테이너 객체가 됩니다. 저는 이번 포스팅에서 싱글 쓰레드 스트림을 활용한 예제를 살펴보겠습니다.
이번 예제는 학생들 중에서 남학생만 수집하는 MaleStudent 컨테이너입니다. 이를 먼저 정의하겠습니다.
아래는 main 메소드입니다.
주석 처리를 하지 않은 부분은 첫 번째로 주석 처리한 코드를 간략하게 표현한 것입니다. 그리고 두 번쨰로 주석 처리한 코드는 Collector 없이 사용자 정의 컨테이너에 요소를 수집하는 방식으로 작성된 것입니다.
정리
지금까지 요소를 수집하는 collect() 메소드에 대해 알아 보았습니다. 하지만, collect() 메소드는 컬렉션의 요소들을 그룹핑하는 역할도 합니다. 이 부분은 다음 시간에 다뤄보겠습니다.
참고 서적
이것이 자바다. - 저자 신용권
'프로그래밍 언어 > Java' 카테고리의 다른 글
JUnit5 필수 개념 정리 (JAVA) (0) | 2021.02.06 |
---|---|
Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [3 - groupingBy()] (JAVA) (0) | 2021.01.10 |
Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [1] (JAVA) (0) | 2021.01.07 |
Stream이란? - 중간 처리 메소드의 종류와 사용 방법 (JAVA) (0) | 2021.01.07 |
Stream이란? - 스트림 파이프라인 (JAVA) (2) | 2021.01.06 |
댓글