해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.
좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!
6.1 컬렉터란 무엇인가
Entity
Dish.java
package Part2.Chapter6.Chapter6_6_1.entity;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return this.name;
}
public int getCalories() {
return this.calories;
}
public Type getType() {
return this.type;
}
public boolean isVegetarian() {
return vegetarian;
}
@Override
public String toString() {
return this.name;
}
public enum Type {
MEAT, FISH, OTHER
}
}
Main
package Part2.Chapter6.Chapter6_6_1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import Part2.Chapter6.Chapter6_6_1.entity.Dish;
/*
* 6.1 컬렉터란 무엇인가?
*/
public class Main_6_1 {
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT)
, new Dish("beef", false, 700, Dish.Type.MEAT)
, new Dish("chicken", false, 400, Dish.Type.MEAT)
, new Dish("french fries", true, 530, Dish.Type.OTHER)
, new Dish("rice", true, 350, Dish.Type.OTHER)
, new Dish("season fruit", true, 120, Dish.Type.OTHER)
, new Dish("pizza", true, 550, Dish.Type.OTHER)
, new Dish("prawns", false, 300, Dish.Type.FISH)
, new Dish("salmon", false, 450, Dish.Type.FISH));
/*
* 6.1.1 고급 리듀싱 기능을 수행하는 컬렉터
*
* 훌륭하게 설계된 함수형 API의 또 다른 장점은 높은 수준의 조합성과 재사용성을 꼽을 수 있다.
* collect로 결과를 수집하는 과정을 간단화하고 유영한 방식으로 정의할 수 있다.
* 스트림에 collect를 호출하면 스트림의 요소에(컬렉터로 파라미터화된) 리듀싱 연산이 수행된다.
*
* 보통 함수를 요소로 변환 할 때는 컬렉터를 적용하며 최종 결과를 저장하는 자료구조에 값을 누적한다.
* (toList 처럼 데이터 자체를 변환하는 것보다는 데이터 저장 구조를 변환할 때가 많다.)
*
* 아래 코드는 Dish.Type 기준으로 음식명을 그룹화 한 코드이다.
*/
Map<Dish.Type, List<Dish>> dishResult = new HashMap<>();
for(Dish dish : menu) {
Dish.Type type = dish.getType();
List<Dish> dishType = dishResult.get(type);
if(dishType == null) {
dishType = new ArrayList<>();
dishResult.put(type, dishType);
}
dishType.add(dish);
}
System.out.println("명령형 버전 : ");
System.out.println(dishResult);
System.out.println("스트림 버전 : ");
System.out.println(menu.stream().collect(Collectors.groupingBy(Dish::getType)));
/*
* 6.1.2 미리 정의된 컬렉터
*
* groupingBy 같이 Collectors 클래스에서 제공하는 팩토리 메서드의 기능을 설명한다.
* Collectors에서 제공하는 메서드의 기능은 크게 세 가지로 구분할 수 있다.
* - 스트림 요소를 하나의 값으로 리듀스하고 요약
* - 요소 그룹화
* - 요소 분할
*/
}
}
6.2 리듀싱과 요약
Entity
Dish.java
package Part2.Chapter6.Chapter6_6_2.entity;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return this.name;
}
public int getCalories() {
return this.calories;
}
public Type getType() {
return this.type;
}
public boolean isVegetarian() {
return vegetarian;
}
@Override
public String toString() {
return this.name;
}
public enum Type {
MEAT, FISH, OTHER
}
}
Main
package Part2.Chapter6.Chapter6_6_2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import Part2.Chapter6.Chapter6_6_2.entity.Dish;
/*
* 6.2 리듀싱과 요약
*
* 컬렉터(Stream.collect 메서드의 인수)로 스트림의 항목을 컬렉션으로 재구성할 수 있다.
* 좀 더 일반적으로 컬렉터로 스트림의 모든 항목을 하나의 결과로 합칠수 있다.
* 트리를 구성하는 다수준 맵, 메뉴의 칼로리 합계를 가리키는 단순한 정수 등 다양한 형식으로
* 결과를 도출될 수 있다.
*/
public class Main_6_2 {
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT)
, new Dish("beef", false, 700, Dish.Type.MEAT)
, new Dish("chicken", false, 400, Dish.Type.MEAT)
, new Dish("french fries", true, 530, Dish.Type.OTHER)
, new Dish("rice", true, 350, Dish.Type.OTHER)
, new Dish("season fruit", true, 120, Dish.Type.OTHER)
, new Dish("pizza", true, 550, Dish.Type.OTHER)
, new Dish("prawns", false, 300, Dish.Type.FISH)
, new Dish("salmon", false, 450, Dish.Type.FISH));
/*
* 첫 번째 예제로 counting() 이라는 팩토리 메서드가 반환하는 컬렉터로 메뉴에서 요리 수를 계산한다.
* counting 컬렉터는 다른 컬렉터와 함께 사용할 때 위력을 발휘한다.
*/
System.out.println("6.2 리듀싱과 요약 - Collectors.counting 사용 : " + menu.stream().collect(Collectors.counting()) );
/*
* 아래처럼 불필요한 과정을 생략할 수 있다.
*/
System.out.println("6.2 리듀싱과 요약 - count 메서드 사용 : " + menu.stream().count() );
/*
* 6.2.1 스트림값에서 최대값과 최소값 검색
*
* 메뉴에서 칼로리가 가장 높거나 낮은 요리를 찾는다고 가정하자.
* Collectors.maxBy, Collectors.minBy 두 개의 메서드를 이용할 수 있다.
* 두 컬렉터는 스트림의 요소를 비교하는데 사용할 Comparator를 인수로 받는다.
*
* Optional<Dish>는 만약 menu가 비어있다면 그 어떤 요리도 반환되지 않을 것이다.
* 자바 8은 값을 포함하거나 포함하지 않을 수 있는 컨테이너 Optional을 제공한다.
*
* 또한 스트림에 있는 객체의 숫자 필드의 합계나 평균 등을 반환하는 연산에도 리듀싱 기능이
* 자주 사용된다. 이러한 연산을 요약(summarization)연산이라 부른다.
*/
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
System.out.print("6.2.1 스트림값에서 최대값과 최소값 검색- Collectors.maxBy 사용 : ");
System.out.println("이름 = " + mostCalorieDish.get() + ", 칼로리 = " + mostCalorieDish.get().getCalories() );
mostCalorieDish = menu.stream().collect(Collectors.minBy(dishCaloriesComparator));
System.out.print("6.2.1 스트림값에서 최대값과 최소값 검색 - Collectors.minBy 사용 : ");
System.out.println("이름 = " + mostCalorieDish.get() + ", 칼로리 = " + mostCalorieDish.get().getCalories() );
/*
* 6.2.2 요약 연산
*
* Collectors 클래스는 Collectors.summingInt라는 특별한 요약 팩토리 메서드를 제공한다.
* summingInt는 객체를 int로 매핑하는 함수를 인수로 받고 인수로 전달된 함수는 객체를 int로
* 매핑한 컬렉터를 반환한다.
* 그리고 summingInt가 collect 메서드로 전달되면 요약 작업을 수행한다.
*
* 다음은 메뉴 리스트의 총 칼로리를 계산하는 코드이다.
*/
System.out.println("6.2.2 요약 연산 - Collectors.summingInt : "
+ menu.stream().collect(Collectors.summingInt(Dish::getCalories)) );
/*
* Collectors.summingLong과 Collectors.summingDouble도 summingInt와 같은 방식으로 동작하며
* 각각 long 또는 double 형식의 데이터로 요약한다는 점만 다르다.
*
* 이러한 단순 합계 외 평균값 계산 등의 연산도 요약 기능으로 제공된다.
* 즉, Collectors.averagingInt, Collectors.averagingLong, Collectors.averagingDouble 등
* 다양한 숫자 집합의 평균을 계산할 수 있다.
*/
System.out.println("6.2.2 요약 연산 - Collectors.averagingInt : "
+ menu.stream().collect(Collectors.averagingInt(Dish::getCalories)) );
/*
* 간혹 위 연산 중 두 개 이상의 연산을 한번에 수행해야 할 때도 있다.
* 이런 상황에서는 팩토리 메서드 summarizingInt가 반환하는 컬렉터를 사용할 수 있다.
*
* int뿐 아니라 long이나 double에 대응하는 Collectors.summarizingLong, Collectors.summarizingDouble 메서드와
* 관련된 LongSummaryStatistics, DobuleSummaryStatistics 클래스도 있다.
*/
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
System.out.println("6.2.2 요약 연산 - Collectors.summarizingInt : " + menuStatistics);
/*
* 6.2.3 문자열 연결
*
* 컬렉터에서 joining 팩토리 메서드를 이용하면 스트림의 각 객체에 toString 메서드를 호출해서
* 추출한 모든 문자열을 하나의 문자열로 연결해서 반환한다.
*
* 아래 코드는 메뉴의 모든 요리명을 연결하는 코드다.
*/
System.out.println("6.2.3 문자열 연결 - joining : "
+ menu.stream().map(Dish::getName).collect(Collectors.joining()));
/*
* joining 메서드는 내부적으로 StringBuilder를 이용해서 문자열을 만든다.
* 연결된 두 요소 사이에 구분 문자열을 넣을 수 있도록 오버로드된 joining 팩토리 메서드도 있다.
*/
System.out.println("6.2.3 문자열 연결 - joining : "
+ menu.stream().map(Dish::getName).collect(Collectors.joining(", ")));
/*
* 6.2.4 범용 리듀싱 요약 연산
*
* 모든 컬렉터는 reducing 팩토리 메서드도 정의할 수 있다.
* 즉, 범용 Collectors.reducing으로도 구현할 수 있다.
* 이전까지 범용 팩토리 메서드 대신 특화된 컬렉터를 사용한 이유는 프로그래밍적 편의성 때문이다.
*
* 아래 코드는 reducing 메서드로 메뉴의 모든 칼로리 합계이다.
*/
System.out.println("6.2.4 범용 리듀싱 요약 연산 - 모든 칼로리 합계 : "
+ menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j) ));
/*
* reducing은 세 개의 인수를 받는다.
* - 첫 번째 인수는 리듀싱 연산의 시작값이거나 스트림에 인수가 없을 때 반환값.
* (숫자 합계에서는 인수가 없을 때 반환값으로 0이 적합하다.)
* - 두 번째 인수는 요리를 칼로리 정수로 변환할 때 사용한 변환 함수이다.
* - 세 번째 인수는 같은 종류의 두 항목을 하나의 값으로 더하는 BinaryOperator다.
*
* 다음 코드 처럼 한 개의 인수를 가진 reducing을 이용해서 칼로리가 가장 높은 요리를 찾는 방법이다.
*/
System.out.println("6.2.4 범용 리듀싱 요약 연산 - 칼로리가 가장 높은 요리 : "
+ menu.stream().collect(Collectors.reducing((d1, d2)
-> d1.getCalories() > d2.getCalories()? d1 : d2)).get() );
/*
* 한 개의 인수를 갖는 reducing 팩터리 메서드는 세 개의 인수를 갖는 reducing 메서드에서
* 스트림의 첫 번째 요소를 시작 요소, 즉 첫 번째 인수로 받으며 자신을 그대로 반환하는
* 항등함수(identity function)를 두 번째 인수로 받는 상황에 해당한다.
*
* 즉, 한 개의 인수를 갖는 reducing 컬렉터는 시작값이 없으므로 빈 스트림이 넘겨졌을 때
* 시작값이 설정되지 않는 상황이 생기므로 Optional<Dish> 객체를 반환한다.
*/
/*
* collect와 reduce
*
* 위 코드는 의미론적인 문제와 실용적인 문제 등 두 가지 문제가 발생한다.
* collect 메서드는 도출 하려는 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드인 반면
* reduce는 두 값을 하나로 도출하는 불변형 연산이라는 점에서 의미론적인 문제가 일어난다.
* 아래 코드에 reduce메서드는 누적자로 사용된 리스트를 변환시키므로 reduce를 잘못 활용한 예이다.
* 여러 스레드가 동시에 같은 데이터 구조체를 고치면 리스트 자체가 망가지므로 리듀싱 연산을 병렬로
* 수행할 수 없다는 점도 문제다.
* 이 문제를 해결하려면 매번 새로운 리스트를 할당해야 하고 따라서 객체를 할당하느라 성능이 저하 될 것이다.
* 가변 컨테이너 관련 작업이면서 병렬성을 확보하려면 collect 메서드로 리듀싱 연산을 구현하는 것이 바람직 한다.
*/
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
System.out.println(stream.reduce(new ArrayList<Integer>()
, (List<Integer> l, Integer e) -> {
l.add(e);
return l;
}
,(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);
return l1;
}));
/*
* 컬렉션 프레임워크 유연성 : 같은 연산도 다양한 방식으로 수행할 수 있다.!
* '6.2.4 범용 리듀싱 요약 연산 - 모든 칼로리 합계' 예제에서 람다 표현식 대신
* Integer클래스의 sum 메서드 레퍼런스를 이용하면 코드를 좀 더 단순화 할 수 있다.
*/
System.out.println("모든 칼로리 합계(sum 메서드 레퍼런스) : " + menu.stream()
.collect(Collectors.reducing(0, Dish::getCalories, Integer::sum)));
System.out.println(menu.stream()
.collect(
/*
* counting 컬렉터도 세 개의 인수를 갖는 reducing 팩토리 메서드를 이용해서
* 구현할 수 있다.
*/
counting()
).longValue()
);
/*
* 자신의 상황에 맞는 최적의 해법 선택
*
* 스트림 인터페이스에서 직접 제공하는 메서드를 이용하는 것보다 컬렉터를 이용하는 코드가
* 더 복잡하다는 사실도 보여준다.
* 코드가 좀 더 복잡한 대신 재사용성과 커스터마이즈 가능성을 제공하는 높은 수준의 추상화와
* 일반화를 얻을 수 있다.
*
* 문제를 해결할 수 있는 다양한 해결 방법을 확인한 다음에 가장 일반적으로 문제에 특화된 해결책을
* 고르는 것이 바람직하다.
*/
}
public static <T> Collector<T, ?, Long> counting() {
/*
* 스트림의 Long 객체 형식의 요소를 1로 변환한 다음에 모두 더할 수 있다.
*/
return Collectors.reducing(0L, e -> 1L, Long::sum);
}
}
6.3 그룹화
Entity
Dish.java
package Part2.Chapter6.Chapter6_6_3.entity;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return this.name;
}
public int getCalories() {
return this.calories;
}
public Type getType() {
return this.type;
}
public boolean isVegetarian() {
return vegetarian;
}
@Override
public String toString() {
return this.name;
}
public enum Type {
MEAT, FISH, OTHER
}
}
Main
package Part2.Chapter6.Chapter6_6_3;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import Part2.Chapter6.Chapter6_6_3.entity.Dish;
/*
* 6.3 그룹화
*
* 데이터 집합을 하나 이상의 특성으로 분류해서 그룹화하는 연산도 데이터베이스에서 많이 수행되는 작업이다.
* 자바 8의 함수형을 이용하면 가독성 있는 한 줄의 코드로 그룹화를 구현할 수 있다.
*/
public class Main_6_3 {
public enum CaloricLevel {DIET, NORMAL, FAT}
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT)
, new Dish("beef", false, 700, Dish.Type.MEAT)
, new Dish("chicken", false, 400, Dish.Type.MEAT)
, new Dish("french fries", true, 530, Dish.Type.OTHER)
, new Dish("rice", true, 350, Dish.Type.OTHER)
, new Dish("season fruit", true, 120, Dish.Type.OTHER)
, new Dish("pizza", true, 550, Dish.Type.OTHER)
, new Dish("prawns", false, 300, Dish.Type.FISH)
, new Dish("salmon", false, 450, Dish.Type.FISH));
/*
* 아래 코드는 고기를 포함한 그룹, 생성을 포함한 그룹, 나머지 그룹으로 메뉴화한 코드이다.
*
* 스트림의 각 요리에서 Dish.Type과 일치하는 모든 요리를 추출하는 함수를 groupingBy 메서드로 전달했다.
* 이 함수를 기준으로 스트림이 그룹화하므로 이를 "분류 함수(classification function)"라고 부른다.
*/
System.out.println(menu.stream()
.collect(Collectors.groupingBy(Dish::getType)) );
/*
* 아래 코드 같이 400칼로리 이하를 'diet'로 400 ~ 700칼로리를 'normal'로 700 칼로리 초과를 'fat' 요리로
* 분류한다고 가정하면 Dish 클래스에는 이러한 연산에 필요한 메서드가 없으므로 메서드 레퍼런스를 분류 함수로
* 사용할 수 없다. 따라서 메서드 레퍼런스 대신 람다 표현식으로 필요한 로직을 구현할 수 있다.
*/
System.out.println(menu.stream()
.collect(Collectors.groupingBy(dish -> {
if(dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if(dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else {
return CaloricLevel.FAT;
}
})) );
/*
* 6.3.1 다수준 그룹화
*
* 두 인수를 받는 팩토리 메서드 Collectors.groupingBy를 이용해서 항목을 다수준으로 그룹화 할 수 있다.
* Collectors.groupingBy는 일반적인 분류 함수와 컬렉터를 인수로 받는다.
* 즉, 바깥 쪽 groupingBy 메서드에 스트림의 항목을 분류할 두 번째 기준을 정의하는 내부 groupingBy를
* 전달해서 두 수준으로 스트림의 항목을 그룹화할 수 있다.
*
* 보통 groupingBy의 연산을 버킷(bucket - 물건을 담을 수 있는 양동이) 개념으로 생각하면 쉽다.
* 첫 번째 groupingBy는 각 키의 버킷을 만든다. 그리고 준비된 각각의 버킷을 서버스트림 컬렉터로
* 채워가기를 반복하면서 n수준 그룹화를 달성한다.
*/
System.out.println(menu.stream()
.collect(Collectors.groupingBy(
/*
* 외부 맵은 첫 번째 수준의 분류 함수에서 분류한 키값(fish, meat, other)를 갖는다.
*/
Dish::getType
/*
* 그리고 외부 맵의 값은 두 번째 수준의 분류 함수의 기준 'normal', 'diet', 'fat'을 키값을 갖는다.
* 최종적으로 두 수준의 맵은 첫 번째 키와 두 번째 키의 수준에 부합하는 요소 리스트를 값(salmon, pizza)으로 갖는다.
* 다수준 그릅화 연산은 다양한 수준으로 확장할 수 있따.
* 즉, n수준 그룹화의 결과는 n수준 트리 구조로 표현되는 n수준 맵이 된다.
*/
, Collectors.groupingBy(dish -> {
if(dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if(dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else {
return CaloricLevel.FAT;
}
})
)
));
/*
* 6.3.2 서브그룹으로 데이터 수집
*
* 첫 번쨰 groupingBy로 넘겨주는 컬렉터의 형식은 제한이 없다.
* 예를 들어 다음 코드처럼 groupingBy 컬렉터에 두 번째 인수로 counting 컬렉터를 전달해서
* 메뉴에서 요리의 수를 종류별로 계산할 수 있다.
*
* 분류 함수 한 개의 인수를 갖는 groupingBy(f)는 사실 groupingBy(f, toList())의 축약형이다.
*/
System.out.println("6.3.2 서브그룹으로 데이터 수집 : " + menu.stream()
.collect(Collectors.groupingBy(Dish::getType, Collectors.counting())) );
/*
* 요리의 종류를 분류하는 컬렉터로 메뉴에서 가장 높은 칼로리를 가진 요리를 아래와 같이 재구현할 수 있다.
*/
System.out.println("6.3.2 서브그룹으로 데이터 수집 : " + menu.stream()
.collect(Collectors.groupingBy(Dish::getType
, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))) ));
/*
* 컬렉터 결과를 다른 형식에 적용하기
*
* 위 코드에서 마지막 그룹화 연산에서 맵의 모든 값을 Optional로 감쌀 필요가 없기 때문에
* Optional을 삭제 할 수 있다.
* 즉, 다음 처럼 팩토리 메서드 Collectors.collectingAndThen으로 컬렉터가 반환한 결과를
* 다른 형식으로 활용할 수 있다.
*
* 펙토리 메서드 collectingAndThen은 적용할 컬렉터와 변환 함수를 인수로 받아 다른 컬렉터를 반환한다.
* 반환되는 컬렉터는 기존 컬렉터의 래퍼 역할을 하며 collect의 마지막 과정에서 변환 함수로 자신이 반환하는
* 값을 매핑한다.
*
* 아래 예제는 maxBy로 만들어진 컬렉터가 감싸지는 컬렉터며 변환 함수 Optional::get으로 반환된 Optional에
* 포함된 값을 추출한다.
*
* 이미 언급했듯이 리듀싱 컬렉터는 Optional.empty()를 반환하지 않으므로 안전한 코드이다.
*/
System.out.println("6.3.2 서브그룹으로 데이터 수집 : " + menu.stream()
.collect(Collectors.groupingBy(
/*
* 요리의 종류에 따라 메뉴 스트림을 서브스트림으로 그룹화 한다.
*/
Dish::getType
/*
* Collectors.collectingAndThen 컬렉터는 세 번째 컬렉터 maxBy를 감싼다.
*/
, Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))
/*
* 리듀싱 컬렉터가 서브스트림에 연산을수행한 결과에 collectingAndThen의 Optional::get
* 변환 함수가 적용된다.
*/
, Optional::get
)
)
) );
/*
* groupingBy와 함께 사용하는 다른 컬렉터 예제
*
* 일반적으로 스트림에서 같은 그룹으로 분류된 모든 요소에 리듀싱 작업을 수행할 때는
* 팩토리 메서드 groupingBy에 두 번째 인수로 전달한 컬렉터를 사용한다.
*
* 예를 들어 메뉴에 있는 모든 요리의 칼로리 합계를 구하려고 만든 컬렉터를 재사용할 수 있다.
*/
System.out.println("groupingBy와 함께 사용하는 다른 컬렉터 예제 : " + menu.stream()
.collect(Collectors.groupingBy(
Dish::getType
, Collectors.summingInt(Dish::getCalories) )
) );
/*
* 이 외에도 mapping 메서드로 만들어진 컬렉터도 groupingBy와 자주 사용된다.
* mapping 메서드는 스트림의 인수를 변환하는 함수와 변환 함수의 결과 객체를 누적하는 컬렉터를
* 인수로 받는다.
* mapping은 입력 요소를 누적하기 전에 매핑 함수를 적용해서 다양한 형식의 객체를 주어진 형식의
* 컬렉터에 맞게 변환하는 역할을 한다.
* 다음 코드처럼 groupingBy와 mapping 컬렉터를 합친 기능이다.
*/
System.out.println("groupingBy와 함께 사용하는 다른 컬렉터 예제 : " + menu.stream()
.collect(Collectors.groupingBy(
Dish::getType
, Collectors.mapping(
// 스트림의 인수를 변환하는 함수
dish -> {
if(dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if(dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else {
return CaloricLevel.FAT;
}
}
// 변환 함수의 결과 객체를 누적하는 컬렉터.
, Collectors.toCollection(HashSet::new) )
/*, Collectors.toSet() )*/
)
) );
}
}
요약
- collect는 스트림의 요소를 요약 결과로 누적하는 다양한 방법(컬렉터라 불리는)을 인수로 갖는 최종 연산이다.
- 스트림의 요소를 하나의 값으로 리듀스하고 요약하는 컬렉터뿐 아니라 최소값, 최대값, 평균값을 계산하는 컬렉터 등이 미리 정의되어 있다.
- 미리 정의된 컬렉터인 groupingBy로 스트림의 요소를 그룹화하거나, partitioningBy로 스트림의 요소를 분할할 수 있다.
- 컬렉터는 다수준의 그룹화, 분할, 리듀싱 연산에 적합하게 설계되어 있다.
- Collector 인터페이스에 정의된 메서드를 구현해서 커스텀 컬렉터를 개발할 수 있다.