Chapter6: 스트림으로 데이터 수집
이 장에서는 다음 항목을 설명한다.
- Collectors 클래스로 컬렉션을 만들고 사용하기
- 하나의 값으로 데이터 스트림 리듀스하기
- 특별한 리듀싱 요약 연산
- 데이터 그룹화와 분할
- 자신만의 커스텀 컬렉터 개발
collect 메서드에 Collecter 파라미터를 사용하여 원하는 연산을 간결하게 구현할 수 있다!
예제) 통화별로 트랜잭션을 그룹화한 코드
// 스트림이 아닐 때
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction : transactions) {
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
// 스트림 사용
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));
- 스트림을 사용할 때 훨씬 간결한 코드로 작성이 된다.
1. 컬렉터란 무엇인가?
- 함수형 프로그래밍에서는 ‘무엇’을 원하는지 직접 명시할 수 있어서 어떤 방법으로 이를 얻을지는 신경쓸 필요가 없다.
- collect 메서드에 Collecter 인터페이스를 구현하여 전달하면 된다.
- 명령형 코드보다 가독성과 유지보수성이 훨씬 좋다.
1) 고급 리듀싱 기능을 수행하는 컬렉터
- 훌륭하게 설계된 함수형 API의 또 다른 장점은 높은 수준의 조합성과 재사용성을 꼽을 수 있다.
- collect로 결과를 수집하는 과정을 간단하면서도 유연한 방식으로 정의할 수 있는 점이 최대의 강점이다.
- 스트림에 collect를 호출하면 스트림의 요소에 리듀싱 연산이 수행된다. (내부적으로 수행)
- 보통 함수를 요소로 변환할 때는 컬렉터를 적용하며 최종 결과를 저장하는 자료구조에 값을 누적한다.
- Collectors 유틸리티 클래스는 자주 사용되는 컬렉터 인스턴스의 정적 팩터리 메서드를 제공한다.
2) 미리 정의된 컬렉터
Collectors에서 제공하는 메서드의 기능 3가지
- 스트림 요소를 하나의 값으로 리듀스하고 요약
- 요소 그룹화
- 요소 분할
리듀싱과 요약 관련 기능 수행 컬렉터
- 다양한 계산을 수행할 때 이들 컬렉터를 유용하게 활용한다.
스트림 요소를 그룹화
- 다수준으로 그룹화하거나 각각의 결과 서브그룹에 추가로 리듀싱 연산을 적용할 수 있도록 다양한 컬렉터를 조합하는 방법을 배운다.
분할
- 한 개의 인수를 받아 불리언으로 반환하는 함수, 즉 프레디케이트를 그룹화 함수로 사용한다.
2. 리듀싱과 요약
counting() 메서드 - 메뉴에서 요리 수를 계산
long howManyDishes = menu.stream().collect(Collectors.counting());
long howManyDishes = menu.stream().collect(counting()); // static import 시
long howManyDishes = menu.stream().count; // 단순화
1) 스트림값에서 최댓값과 최솟값을 검색
Collectors.maxBy, Collectors.minBy는 최댓값과 최솟값을 계산한다.
- 두 컬렉터는 요소를 비교하는 데 사용할 Comparator를 인수로 받는다.
칼로리로 요리를 비교하는 Comparator
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCaloriesDish = menu.stream().collect(maxBy(dishCaloriesComparator));
- Optional은 menu가 비어있을 시 요리가 반환되지 않음을 방지한다.
2) 요약 연산
Collectors.summingInt 라는 특별한 요약 팩터리 메서드 (summingLong, summingDouble)
- summingInt는 객체를 int로 매핑하는 함수를 인수로 받는다.
- summingInt의 인수로 전달된함수는 객체를 int로 매핑한 컬렉터를 반환한다.
메뉴 리스트의 총 칼로리를 계산하는 코드
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
- 칼로리로 매핑된 각 요리의 값을 탐색하면서 초깃값 0으로 설정되어 있는 누적자에 칼로리를 더한다.
Collectors.averagingInt 평균 값 계산 (averagingLong, averagingDouble)
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalores));
Collectors.summarizingInt 두 개 이상의 연산을 한 번에 수행한다.
IntSummaryStatistics = menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
- IntSummaryStatistics 클래스로 모든 정보가 수집된다.
3) 문자열 연결
Collectors.joining 팩터리 메서드
스트림의 각 객체에 toString 메서드를 호출해서 추출한 모든 문자열을 하나의 문자열로 연결해서 반환한다.
메뉴의 모든 요리명을 연결하는 코드
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
- joining 메서드는 내부적으로 StringBuilder를 이용해 문자열을 하나로 만든다.
연결된 요소 사이에 구분 문자열을 넣는 joining 메서드
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
4) 범용 리듀싱 요약 연산
PREVIOUS모던 자바 인 액션 - 스트림 활용