주난v 개발 성장기

[자바 8인 액션] 6장. 스트림으로 데이터 수집 본문

개발 성장기/JAVA

[자바 8인 액션] 6장. 스트림으로 데이터 수집

주난v 2020. 7. 21. 09:19

스트림의 연산은 filter, map과 같은 중간 연산 / count, findFirst, forEach, reduce 등의 최종 연산

 

중간 연산 : 한 스트림을 다른 스트림으로 변환, 여러 연산을 연결할 수 있다.

최종 연산 : 스트림 요소를 소비해서 최종 결과를 도출

 

컬렉터란?

스트림의 요소를 어떤 식으로 도출할지 지정

.collect(Collectors.toList());

 

collect를 호출하면 스트림의 요소에 리듀싱 연산이 수행된다.

collect에서는 리듀싱 연산을 이용해서 스트림의 각 요소를 방문하면서 컬렉터가 작업을 처리한다.

 

Collector 인터페이스의 메서드를 어떻게 구현하느냐에 따라 스트림에 어떤 리듀싱 연산을 수행할지 결정

Collectors 클래스는 정적 팩토리 메서드를 제공한다.

 

리듀싱과 요약

- 스트림의 항목을 컬렉션으로 재구성할 수 있다. (즉, 컬렉터로 스트림의 모든 항목을 하나의 결과로 합칠 수 있다.)

- 요약 : Collectors.maxBy, Collectors.minBy를 이용해서 최대 최소 혹은 합계 평균을 반환하는 리듀싱 기능이 자주 사용 -> 요약 연산
            (summingInt, summingDouble...)

 

문자열 연결

- joining

String shortMenu = menu.stream().map(Dish::getName).collect(joining(",");

 

지금까지 살펴본 스트림 요소 수 계산, 최대 최소 합계 평균 등의 컬렉터는 reducing 팩토리 메서드로도 정의 가능 (즉, Collectors.reducing)

 

collect vs reduce

collect 대신 reduce를 사용할 수 있다.

 

collect 도출하려는 결과를 누적하는 컨테이너를 변경

reduce 두 값을 하나로 도출하는 불변형 연산....의미론적인 문제

 

여러 스레드가 동시에 같은 데이터 구조체를 고치면 리스트 자체가 망가져버리므로 리듀싱 연산을 병렬로 수행할 수 없다.

 

따라서 가변 컨테이너 작업이면서 병렬성을 확보하려면 collect 메서드로 reducing 연산을 구현해라.

 

그룹화 

Collectors.groupingBy

Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

{FISH = [prawns, salmon], OTHER = [...], MEAT = [...]}

 

Dish.Type으로 일치하는 모든 요리를 추출해서 그룹화되므로 이를 분류 함수라 한다.

 

다수준 그룹화

- Collectors.groupingBy는 일반적인 분류 함수와 컬렉터를 인수로 받는다.

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = 
	menu.stream.collect(
    	groupingBy(Dish::getType,
        	groupingBy(dish -> {
            	if (dish.getCalories() <= 400) {
                	return CaloricLevel.DIET;
                } else if (dish.getCalories() <= 700) {
                	return CaloricLevel.NORMAL;
                } else {
                	return CaloricLevel.FAT;
                }
            })
         )
    );
Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting());

 

컬렉터 결과를 다른 형식에 적용하기

- Collectors.collectingAndThen으로 컬렉터가 반환한 결과를 다른 형식으로 활용할 수 있다.

Map<Dish.Type, Long> typesCount = 
menu.stream().collect(groupingBy(Dish::getType, 
		collectingAndThen(maxBy(compaingInt(Dish::getCalories)),Optional::get)));

 

분할

분할 함수

- 프레디케이트를 분류 함수로 사용하는 특수한 그룹화 기능

Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));

분할의 장점

- 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지 (false = [], true = [] 두 수준의 맵이 반환)

 

Collector 인터페이스

- Collector 인터페이스는 리듀싱 연산을 어떻게 구현할지 제공하는 메서드 집합

(toList, groupingBy, ...)

 

Collector 인터페이스의 메서드

1. supplier : 빈 결과로 이루어진 Supplier 반환

2. accumulator : 결과 컨테이너에 요소 추가 (list.add)

3. finisher : 최종 변환값을 결과 컨테이너로 적용

4. combiner : 두 결과 컨테이너 병합

 

요약

  • collect는 스트림의 요소를 요약 결과로 누적하는 다양한 방법을 인수로 갖는 최종 연산이다.

  • 스트림의 요소를 하나의 값으로 리듀스하고 요약하는 컬렉터 뿐 아니라 최대 / 최소 / 평균을 계산하는 컬렉터도 정의되어 있다.

  • groupingBy로 스트림의 요소를 그룹화하거나, partitioningBy로 요소를 분할 할 수 있다.

  • 커스텀 컬렉터를 개발할 수 있다.