[모던 자바 인 액션] - 스트림으로 데이터 수집

김성혁·2022년 4월 8일
0

모던 자바 인 액션

목록 보기
6/7

중간 연산

  • 한 스트림을 다른 스트림으로 변환하는 연산
  • 여러 연산을 연결할 수 있습니다.
  • 스트림 파이프라인을 구성하며, 스트림의 요소를 소비하지 않습니다.

최종 연산

  • 스트림의 요소를 소비해서 최종 결과를 도출합니다.
  • 스트림 파이프라인을 최적화하면서 계산 결과를 짧게 생략하기도 합니다.

👨🏻‍💻 컬렉터란?

  • Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 결정합니다.
  • ex. toList, groupingBy

collect에서는 내부적으로 리듀싱 연산이 일어나는데 리듀싱 연산을 이용해서 스트림의 각 요소를 방문하면서 컬렉터가 작업을 처리합니다.

컬렉터에서 제공하는 메서드의 기능

  • 스트림 요소를 하나의 값으로 리듀스하고 요약
  • 요소 그룹화
  • 요소 분할

👨🏻‍💻 리듀싱과 요약


요소의 개수 세기

  • counting
    • 팩토리 메서드가 반환하는 컬렉터로 요소 수를 계산
long howManyDishes = menu.stream().count();
->
long howManyDishes = menu.stream().collect(Collectors.counting());

스트림값에서 최댓값과 최솟값 검색

  • Collectors.maxBy, Collectors.minBy
    • 스트림의 요소를 비교하는 데 사용할 Comparator를 인수로 받습니다.
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);

Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));

요약 연산

  • Collectors.summingInt
    • 객체를 int로 매핑하는 함수를 인수로 받습니다.
// 메뉴 리스트의 총 칼로리 계산
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
  • 평균값 계산
    • averagingInt
  • 두 개 이상의 연산을 한 번에 수행할 때
    • summarizingInt: 요소 수, 합계, 평균, 최댓값, 최솟값 등을 한 번에 계산할 수 있습니다.
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

문자열 연결

  • joining
    • 스트림의 각 객체에 toString 메서드를 호출해서 추출한 모든 문자열을 하나의 문자열로 연결해서 반환합니다.
    • joining 메서드는 내부적으로 StringBuilder를 이용해서 문자열을 하나로 만듭니다. Dish 클래스가 요리명을 반환하는 toString 메서드를 포함하고 있다면 map으로 각 요리 이름을 추출하는 과정을 생략 가능
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().collect(joining());

범용 리듀싱 요약 연산

  • Collectors.reducing

하나의 연산을 다양한 방법으로 해결할 수 있습니다. 문제를 해결할 수 있는 다양한 방법을 확인한 다음에 가장 일반적인 문제에 특화된 해결책을 고르는 것이 바람직합니다. 생각에 또 생각을 더하자


👨🏻‍💻 그룹화

  • 데이터 집합을 하나 이상의 특성으로 분류해서 그룹화
  • 그룹화 연산의 결과로 그룹화 함수가 반환하는 키 그리고 각 키에 대응하는 스트림의 모든 항목 리스트를 값으로 갖는 맵이 반환

👨🏻‍💻 분할

  • 분할 함수라 불리는 프레디케이트를 분류 함수로 사용하는 특수한 그룹화 기능
  • 분할 함수는 불리언을 반환하므로 맵의 키 형식은 최대 (참 아니면 거짓을 나타내는) 두 개의 그룹으로 분류
  • partitioningBy
    • 분할의 장점은 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지한다는 것이다.
    • 컬렉터를 두 번째 인수로 전달할 수 있는 오버로드된 버전을 통해 다수준으로 분할하는 기법도 존재

Collectors 클래스의 정적 팩토리 메서드 p.223 참고

👨🏻‍💻 Collector 인터페이스

  • 리듀싱 연산을 어떻게 구현할지 제공하는 메서드 집합으로 구성
public interface Collector<T, A, R> {
	Supplier<A> supplier();
	BiConsumer<A, T> accumulator();
	BinaryOperator<A> combiner();
	Function<A, R> finisher();
	Set<Characteristics> characteristics();
	...
}
  • T는 수집될 스트림 항목의 제네릭 형식
  • A는 누적자, 즉 수집 과정에서 중간 결과를 누적하는 객체의 형식
  • R은 수집 연산 결과 객체의 형식이다.

supplier 메서드 : 새로운 결과 컨테이너 만들기

  • 빈 결과로 이루어진 Supplier를 반환

accumulator 메서드 : 결과 컨테이너에 요소 추가하기

  • 리듀싱 연산을 수행하는 함수를 반환
  • 함수의 반환값은 void, 즉 요소를 탐색하면서 적용하는 함수에 의해 누적자 내부 상태가 바뀌므로 누적자가 어떤 값일지 단정할 수 없다.

finisher 메서드 : 최종 변환값을 결과 컨테이너로 적용하기

  • 스트림 탐색을 끝내고 누적자 객체를 최종 결과로 변환하면서 누적 과정을 끝낼 때 호출할 함수를 반환해야 한다.

combiner 메서드 : 두 결과 컨테이너 병합

  • 스트림의 서로 다른 서브 파트를 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지 정의

Characteristics 메서드

  • 컬렉션의 연산을 정의하는 Characteristics 형식의 불변 집합을 반환
    • 스트림을 병렬로 리듀스할 것인지 그리고 병렬로 리듀스한다면 어떤 최적화를 선택해야 할지 힌트를 제공
  • 다음 세 항목을 포함하는 열거형
    • UNORDERED : 리듀싱 결과는 스트림 요소의 방문 순서나 누적 순서에 영향을 받지 않는다.
    • CONCURRENT : 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있으며 이 컬렉터는 스트림의 병렬 리듀싱을 수행할 수 있다. 컬렉션의 플래그에 UNORDERED를 함께 설정하지 않는다면 데이터 소스가 정렬되어 있지 않은 상황에서만 병렬 리듀싱을 수행할 수 있다.
    • IDENTITY_FINISH : finisher 메서드가 반환하는 함수는 단순히 identity를 적용할 뿐이므로 생략 가능. 따라서 리듀싱 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있다. 또한 누적자 A를 결과 R로 안전하게 형변환할 수 있다.

👨🏻‍💻 커스텀 컬렉터를 구현해서 성능 개선하기

Collector 인터페이스의 각 메서드를 오버라이딩하여 구현 → 성능 향상의 결과

0개의 댓글