이 글은 모던 자바 인 액션 책을 읽고 정리한 내용입니다.
http://www.yes24.com/Product/Goods/77125987
컬렉터란 무엇인가 ?
고급 리듀싱 기능을 수행하는 컬렉터
함수형 API의 또다른 장점 : 높은 수준의 조합성, 재사용성
미리 정의된 컬렉터
Collectors에서 제공하는 메서드의 기능은 3가지로 구분할 수 있다.
- 스트림 요소를 하나의 값으로 리듀스 하고 요약 : 다양한 계산을 수행할 때 유용
- 요소 그룹화 : 다수준으로 그룹화 or 각각의 결과 서브그룹에 추가로 리듀싱 적용
- 요소 분할 : 프레디케이트(boolean을 반환하는 함수)를 그룹화 함수로 사용
리듀싱과 요약
예제 1 : counting()으로 요리 수를 계산
long howManyDishes = menu.stream().collect(Collectors.counting());
이렇게도 생략 가능하다.
long howManyDishes = menu.stream().count();
스트림값에서 최댓값과 최솟값 검색
메뉴에서 칼로리가 가장 높은 요리를 찾는 코드를 보자.
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu
.stream()
.collect(maxBy(dishCaloriesComparator));
스트림에 있는 객체의 숫자 필드의 합계나 평균 등을 반환하는 연산에도 리듀싱 기능이 자주 사용된다. 이런 연산을 요약 연산이라 한다.
요약 연산
1.합계 구하기
Collectors.summingInt
: 인수로 전달된 함수는 객체를 int로 매핑한 컬렉터를 반환 → collect 메서드로 전달되면 요약 작업을 수행한다.
Collectors.summingDouble 메서드는 같은 방식으로 동작하며 각각 long 또는 double 형식의 데이터로 요약한다.
2.평균값 계산
averagingInt, averagingLong, averagingDouble 등으로 다양한 형식으로 이루어진 숫자 집합의 평균도 계산할 수 있다.
두 개 이상의 연산을 한 번에 수행할때는 ?
summarizingInt가 반환하는 컬렉터를 사용할 수 있다.
int뿐 아니라 long이나 double에 대응하는 summarizingLong, summarizingDouble 메서드와 관련된LongSummaryStatistics, DoubleSummaryStatistics클래스도있다.
문자열 연결
Collector에 joining 팩토리 메서드를 이용하면 스트림의 각 객체에 toString 메서드를 호출해서 추출한 모든 문자열을 하나의 문자열로 연결해서 반환한다.
joining 메서드는 내부적으로 StringBuilder를 이용해서 문자열을 하나로 만든다.
범용 리듀싱 요약 연산
위에 살펴본 모든 컬렉터는 reducing 팩토리 메서드로 정의할 수 있다.
collect vs reduce
- 의미론 적 문제
collect 메서드 : 도출하려는 결과를 누적하는 컨테이너를 바꾸도록 설계
reduce 메서드 : 두 값을 하나로 도출하는 불변형 연산 - 실용성 문제
의미론적으로 reduce를 잘못하용하면 실용성 문제도 발생할 수 있다.
여러 스레드가 동시에 같은 데이터 구조체를 고치면 리스트 자체가 망가지므로 병렬로 수행 불가능.
결론 : 가변 컨테이너 관련 작업이면서 병렬성을 확보하고 싶으면 collect 메서드로 리듀싱 연산을 구현하자.
그룹화
Collectors.groupingBy를 이용해서 메뉴를 그룹화할 수 있다. 이런 함수를 분류 함수라고 부른다.
그룹화된 요소 조작
Map<Dish.Type, List<Dish>> caloricDishesByType =
menu.stream()
.collect(groupingBy(Dish: :getType,
filtering(dish -> dish.getCalories() › 500,
toList())));
filtering 메소드는 Collectors 클래스의 또 다른 정적 팩토리 메서드로 프레케이트를 인수로 받는다.
이렇게 하면 목록이 비어있는 FISH도 항목으로 추가되는 것을 확인할 수 있다.
다수준 그룹화
Collcectors.groupingBy를 이용해서 항목을 다수준으로 그룹화 할 수 있다.
서브 그룹으로 데이터 수집
groupingBy 컬렉터에서 두 번째 인수로 counting 컬렉터를 전달해서 메뉴에서 요리의 수를 종류별로 계산할 수 있다.
Map<Dish.Type, Long> typesCount = menu.stream().collect(
groupingBy(Dish::getType, counting())
);
분할
분할 함수는 boolean을 반환하므로 맵의 키 형식은 Boolean이다.
분할의 장점
참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지한다는 것이다.
partitioningBy가 반환한 맵 구현은 참과 거짓 두 가지 키만 포함하므로 더 간결하고 효과적이다.
Collector 인터페이스
Collector 인터페이스는 리듀싱 연산을 어떻게 구현할지 제공하는 메서드 집합으로 구성된다.
Collector 인터페이스
public interface Collector <T, A, R〉 {
Supplier<A> supplier();
BiConsumer<A, >Taccumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set<Characteristics> characteristics();
}
- T : 수집될 스트림 항목의 제네릭 형식
- A : 누적자. 수집 과정에서 중간 결과를 누적하는 객체의 형식
- R : 수집 연산 결과 객체의 형식(대개 컬렉션 형식)
Collector 인터페이스의 메서드 살펴보기
supplier 메서드 : 새로운 결과 컨테이너 만들기
빈 결과로 이루어진 Supplier를 반환해야 한다.
accumulator 메서드 : 결과 컨테이너에 요소 추가하기
리듀싱 연산을 수행하는 함수를 반환 한다.
finisher 메서드 : 최종 변환값을 결과 컨테이너로 적용하기
스트림 탐색을 끝내고 누적자 객체를 최종 결과로 변환하면서 누적 과정을 끝낼 때 호출할 함수를 반환해야 한다.
combiner 메서드 : 두 결과 컨테이너 병합
combiner는 스트림의 서로 다른 서브파트를 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지 정의 한다.
Characteristics 메서드
컬렉터의 연산을 정의하는 Characteristics 형식의 불변 집합을 반환한다.
Characteristics는 다음 세 항목을 포함하는 열겨형이다.
- UNORDERED : 순서에 영향 X
- CONCURRENT : 스트림의 병렬 리듀싱을 수행할 수 있음
- IDENTITY_FINISH : 생략 가능