Collector ์ธํฐํ์ด์ค์ ๊ตฌํ์ Stream์ ์์๋ฅผ ์ด๋ค ์์ผ๋ก ๋์ถํ ์ง ์ง์ ํ๋ค.
Stream์์ collect๋ก ๊ฒฐ๊ณผ๋ฅผ ์์งํ๋ ๊ณผ์ ์์ Collector๋ฅผ ์ด์ฉํ์ฌ ๊ณ ์์ค์ Reducing์ ์ฝ๊ฒ ํ ์ ์๋ค. Collectors ์ ํธ๋ฆฌํฐ Class๋ ์์ฃผ ์ฌ์ฉํ๋ Collector ์ธ์คํด์ค๋ฅผ ์์ฝ๊ฒ ์์ฑํ ์ ์๋ ์ ์ Factory Method๋ฅผ ์ ๊ณตํ๋ค.
-- Collectors.toList()
List<Transaction> transactions = transactionStream.collect(Collectors.toList());
Collectors์์ ์ ๊ณตํ๋ Factory Method์ ๊ธฐ๋ฅ์ 3๊ฐ์ง๋ก ๊ตฌ๋ถํ๋ค.
Factory Method | Return Type | Example |
---|---|---|
toList | List<T> | ์คํธ๋ฆผ์ ๋ชจ๋ ์์๋ฅผ ๋ฆฌ์คํธ๋ก ์์ง |
toSet | Set<T> | ์คํธ๋ฆผ์ ๋ชจ๋ ์์๋ฅผ ์งํฉ์ผ๋ก ์์ง |
toCollection | Collection<T> | ์คํธ๋ฆผ์ ๋ชจ๋ ์์๋ฅผ ํน์ Collection์ผ๋ก ์์ง |
counting | Long | ์คํธ๋ฆผ์ ๋ชจ๋ ์์์ ๊ฐฏ์๋ฅผ ๋ฐํ |
summingInt/Double/Long | Integer/Double/Long | ์คํธ๋ฆผ์ ์์์ ์์ฑ๊ฐ์ ๋ํจ |
averageInt/Double/Long | Integer/Double/Long | ์คํธ๋ฆผ์ ์์์ ์์ฑ ํ๊ท ๊ฐ์ ๊ณ์ฐ |
summarizingInt/Double/Long | Integer/Double/Long | ์คํธ๋ฆผ์ ์์์ ์ต๋๊ฐ, ์ต์๊ฐ, ํฉ๊ณ, ํ๊ท ๋ฑ์ ์ ๋ณด ํต๊ณ ์์ง |
joining | String | ์คํธ๋ฆผ์ ๋ชจ๋ ์์์ toString()์ ํธ์ถํ์ฌ ์ฐ๊ฒฐ |
maxBy | Optional<T> | ์ฃผ์ด์ง Comparator๋ฅผ ์ด์ฉํด์ ์คํธ๋ฆผ์ ์ต๋๊ฐ ์์๋ฅผ Optional๋ก ๊ฐ์ผ ๊ฐ์ผ๋ก ๋ฐํ |
minBy | Optional<T> | ์ฃผ์ด์ง Comparator๋ฅผ ์ด์ฉํด์ ์คํธ๋ฆผ์ ์ต์๊ฐ ์์๋ฅผ Optional๋ก ๊ฐ์ผ ๊ฐ์ผ๋ก ๋ฐํ |
reducing | ๋์ ์๋ฅผ ์ด๊น๊ฐ์ผ๋ก ์ค์ ํ ๋ค์์ BinaryOperator๋ก ์คํธ๋ฆผ์ ๊ฐ ์์๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋์ ์์ ํฉ์ณ ์คํธ๋ฆผ์ ํ๋์ ๊ฐ์ผ๋ก Reducing | |
collectingAndThen | ๋ค๋ฅธ Collector๋ฅผ ๊ฐ์ธ๊ณ ๊ทธ ๊ฒฐ๊ณผ์ ๋ณํ ํจ์ ์ ์ฉ | |
groupingBy | Map<Key, List<T>> | ํ๋์ ์์ฑ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์คํธ๋ฆผ์ ์์๋ฅผ ๊ทธ๋ฃนํํ๋ฉฐ ๊ธฐ์ค ์์ฑ๊ฐ์ ๊ฒฐ๊ณผ ๋งต์ ํค๋ก ์ฌ์ฉ |
partitioningBy | Map<Boolean, List<T>> | Predicate๋ฅผ ์คํธ๋ฆผ์ ๊ฐ ์์์ ์ ์ฉํ ๊ฒฐ๊ณผ๋ก ์์ ๋ถํ |
Stream์์ ์์์ ๊ฐ์๋ฅผ ๋ฐํํ๋ค.
long howManyDishes = menu.stream().collect(Collectors.counting());
Stremam์ ์ต๋๊ฐ๊ณผ ์ต์๊ฐ์ ๋ฐํํ๋ค.
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
-- Collectors.maxBy()
Optional<Dish> mostCaloriesDish = menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
-- Collectors.minBy()
Optional<Dish> leastCaloriesDish = menu.stream().collect(Collectors.minBy(dishCaloriesComparator));
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
double averageCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
count, min, max, sum, average๋ฅผ ๋ชจ๋ ๋ฐํํ๋ SummaryStatistics ํด๋์ค๋ฅผ ๋ฐํํ๋ค.
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
Stream์ ๊ฐ ๊ฐ์ฒด์ toString() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ชจ๋ ๋ฌธ์์ด์ ํ๋์ ๋ฌธ์์ด๋ก ์ฐ๊ฒฐํ์ฌ ๋ฐํํ๋ค.
joining ๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก StringBuilder๋ฅผ ์ด์ฉํ์ฌ ๋ฌธ์์ด์ ํ๋๋ก ๋ง๋ค๋ฉฐ (๊ตฌ๋ถ์, Prefix, Postfix)๋ฅผ ์ธ์๋ก ์ ๋ฌํ๋ค.
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));
-- ํฉ๊ณ
int totalCalories =
menu.stream()
.collect(Collectors.reducing(0, Dish::getCalories, (s, t) -> s + t));
-- ํฉ๊ณ - Integer ํด๋์ค sum ์ฌ์ฉ
int totalCalories =
menu.stream()
.collect(Collectors.reducing(0, Dish::getCalories, Integer::sum));
-- ์ต๋๊ฐ
Optional<Dish> mostCaloriesDish =
menu.stream()
.collect(Collectors.reducing((i, j) -> i.getCalories() < j.getCalories() ? j : i));
Classification Function (๋ถ๋ฅ ํจ์)๋ก Stream ์์๋ฅผ ๊ทธ๋ฃนํ ํ ์ ์๋ค.
-- ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
Java SE 9์ ์ถ๊ฐ๋ Collectors.filtering ๋ฉ์๋๋ก ์์๋ค์ ์กฐ๊ฑด๋ ์ถ๊ฐํ ์ ์๋ค.
-- Collectors.filtering ์ฌ์ฉ
Map<Dish.Type, List<Dish>> caloricDishesByType =
menu.stream().collect(
Collectors.groupingBy(
Dish::getType,
Collectors.filtering(dish -> dish.getCalories() > 500, Collectors.toList())
)
);
Collectors ํด๋์ค๋ ๋งคํ ํจ์์ ๊ฐ ํญ๋ชฉ์ ์ ์ฉํ ํจ์๋ฅผ ๋ชจ์ผ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ ๋ค๋ฅธ ์ปฌ๋ ํฐ๋ฅผ ์ธ์๋ก ๋ฐ๋ mpaaing ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
-- Collectors.mapping ์ฌ์ฉ
Map<Dish.Type, List<Dish>> caloricDishesByType =
menu.stream().collect(
Collectors.groupingBy(
Dish::getType,
Collectors.mapping(Dish::getName, Collectors.toList())
)
);
Java SE 9์ ์ถ๊ฐ๋ Collectors.flatMapping ๋ฉ์๋๋ก ๋ ์์ค์ ๋ฆฌ์คํธ๋ฅผ ํ ์์ค์ผ๋ก ํ๋ฉดํํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
Map<Dish.Type, Set<String>> dishNamesByType =
menu.stream()
.collect(
Collectors.groupingBy(
Dish::getType,
Collectors.flatMapping(dish -> dishTags.get(dish.getName()).stream(), Collectors.toSet())
)
);
groupingBy ๋ฉ์๋๋ฅผ ์ค์ฒฉ ์ฌ์ฉํ์ฌ ๋ค์์ค์ผ๋ก ์คํธ๋ฆผ์ ํญ๋ชฉ์ ๊ทธ๋ฃนํํ ์ ์๋ค. n์์ค ๊ทธ๋ฃนํ์ ๊ฒฐ๊ณผ๋ n์์ค ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ํํ๋๋ n์์ค ๋งต์ด ๋๋ค.
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream()
.collect(
Collectors.groupingBy(
Dish::getType,
Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}, Collectors.toList())
)
);
-- ํ์
๋ณ ์์ ๊ฐฏ์
Map<Dish.Type, Long> typesCount =
menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
-- ํ์
๋ณ๋ก ๊ฐ์ฅ ๋์ ์นผ๋ก๋ฆฌ๋ฅผ ๊ฐ์ง ์์
Map<Dish.Type, Optional<Dish>> mostCaloricByType =
menu.stream().collect(
Collectors.groupingBy(
Dish::getType,
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))
)
);
์ปฌ๋ ํฐ๊ฐ ๋ฐํํ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅธ ํ์์ผ๋ก ๋ณํํ ์ ์๋ค.
-- ์ปฌ๋ ํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅธ ํ์์ ์ ์ฉ
Map<Dish.Type, Dish> mostCaloricByType =
menu.stream().collect(
Collectors.groupingBy(
Dish::getType,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
Optional::get
)
)
);
Partitioning Function (๋ถํ ํจ์)๋ผ ๋ถ๋ฆฌ๋ Predicate๋ฅผ ๋ถ๋ฅ ํจ์๋ก ์ฌ์ฉํ๋ ํน์ํ ๊ทธ๋ฃนํ ๊ธฐ๋ฅ์ด๋ค. ๋ถํ ํจ์๋ Boolean์ ๋ฐํํ๋ฏ๋ก ๋งต์ ํค ํ์์ Boolean์ด๋ฉฐ ๋ ๊ทธ๋ฃน(True or False)์ผ๋ก ๋๋์ด์ง๋ค.
-- ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
-- groupingBy์ ๊ฐ์ด ์ฌ์ฉ
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType =
menu.stream().collect(
Collectors.partitioningBy(
Dish::isVegetarian,
Collectors.groupingBy(Dish::getType)
)
);
Collector Interface๋ ์๋์ ๊ฐ๋ค.
public interface Collector<T, A, R\> {
Supplier<A\> supplier();
BiConsumer<A, T\> accumulator();
Function<A, R\> finisher();
BinaryOperator<A\> combiner();
Set<Characteristics\> characteristics();
}
๋น ๊ฒฐ๊ณผ๋ก ์ด๋ฃจ์ด์ง Supplier๋ฅผ ๋ฐํํ๋ฉฐ, ์์ง ๊ณผ์ ์์ ๋น ๋์ ์ ์ธ์คํด์ค๋ฅผ ๋ง๋๋ ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ํจ์์ด๋ค.
public Supplier<List<T\>> supplier() {
return ArrayList::new;
}
Reducing ์ฐ์ฐ์ ์ํํ๋ ํจ์๋ฅผ ๋ฐํํ๋ฉฐ, ์คํธ๋ฆผ์์ n๋ฒ์งธ ์์๋ฅผ ํ์ํ ๋ ๋ ์ธ์(๋์ ์์ n๋ฒ์งธ ์์)๋ฅผ ํจ์์ ์ ์ฉํ๋ค.
public BiConsumer<List<T\>, T\> accumulator() {
return List::add;
}
์คํธ๋ฆผ ํ์์ ๋๋ด๊ณ ๋์ ์ ๊ฐ์ฒด๋ฅผ ์ต์ข ๊ฒฐ๊ณผ๋ก ๋ณํํ๋ฉด์ ๋์ ๊ณผ์ ์ ๋๋ผ ๋ ํธ์ถํ ํจ์๋ฅผ ๋ฐํํ๋ค.
public Function<List<T\>, List<T\>> finisher() {
return Function.identity();
}
์คํธ๋ฆผ์ ์๋ก ๋ค๋ฅธ ์๋ธ ํํธ๋ฅผ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ๋ ๋์ ์๊ฐ ์ด ๊ฒฐ๊ณผ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ ์ํ๋ค. ์คํธ๋ฆผ์ Reducing ์์ ์ ๋ณ๋ ฌ๋ก ์ํํ ์ ์๋ค.
public BinarayOperator<List<T\>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
}
}
Collector์ ์ฐ์ฐ์ ์ ์ํ๋ Characteristics ํ์์ ๋ถ๋ณ ์งํฉ์ ๋ฐํํ๋ค.
**Characteristics๋ ๋ค์ 3 ํญ๋ชฉ์ ํฌํจํ๋ ์ด๊ฑฐํ์ด๋ค.
-- ์ปค์คํ
toList ๊ตฌํ
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
@Override
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
@Override
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT);
}
}
List<Dish> dishes = menu.stream().collect(new ToListCollector<>());