Collector

์ปฌ๋ ‰ํ„ฐ๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

Collector ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์€ ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ์–ด๋–ค ์‹์œผ๋กœ ๋„์ถœํ• ์ง€ ์ง€์ •ํ•œ๋‹ค.

๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ์œ„ํ•ด toList๋ฅผ Collector ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ groupingBy๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ ํ‚ค ๋ฒ„ํ‚ท์— ๋Œ€์‘ํ•˜๋Š” ์š”์†Œ ๋ณ„๋กœ ๋งต์„ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค.

6.1.1 ๊ณ ๊ธ‰ ๋ฆฌ๋“€์‹ฑ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ปฌ๋ ‰ํ„ฐ

์ปฌ๋ ‰ํ„ฐ์˜ ์ตœ๋Œ€ ๊ฐ•์ ์€ collect๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ์œ ์—ฐํ•œ ๋ฐฉ์‹์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.

์ŠคํŠธ๋ฆผ์—์„œ collect๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด collect์—์„œ๋Š” ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ์„ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”์†Œ๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด์„œ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋ณดํ†ต ํ•จ์ˆ˜๋ฅผ ์š”์†Œ๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ๋Š” ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ฉฐ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ์— ๊ฐ’์„ ๋ˆ„์ ํ•œ๋‹ค.

6.1.2 ๋ฏธ๋ฆฌ ์ •์˜๋œ ์ปฌ๋ ‰ํ„ฐ

Collectors ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋Š” ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์ปฌ๋ ‰ํ„ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์†์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

Collectors์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ์˜ ๊ธฐ๋Šฅ์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„๋œ๋‹ค.

  • ์ŠคํŠธ๋ฆผ ์š”์†Œ๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ’์œผ๋กœ ๋ฆฌ๋“€์Šคํ•˜๊ณ  ์š”์•ฝ

  • ์š”์†Œ ๊ทธ๋ฃนํ™”

  • ์š”์†Œ ๋ถ„ํ• 


6.2 ๋ฆฌ๋“€์‹ฑ๊ณผ ์š”์•ฝ

์ปฌ๋ ‰ํ„ฐ๋กœ ์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์„ ํ•˜๋‚˜์˜ ๊ฒฐ๊ณผ๋กœ ํ•ฉ์น  ์ˆ˜ ์žˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ์˜ˆ์ œ๋กœ counting() ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.

long howManyDishes = menu.stream().collect(Collectors.counting());
//collect ์ƒ๋žต ๊ฐ€๋Šฅ
long howManyDishes = menu.stream().count();

6.2.1 ์ŠคํŠธ๋ฆผ ๊ฐ’์—์„œ ์ตœ๋Œ“๊ฐ’๊ณผ ์ตœ์†Ÿ๊ฐ’ ๊ฒ€์ƒ‰

Collectors.maxBy, Collectors.minBy ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์˜ ์ตœ๋Œ“๊ฐ’๊ณผ ์ตœ์†Ÿ๊ฐ’์„ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‘ ์ปฌ๋ ‰ํ„ฐ๋Š” ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ๋น„๊ตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  Comparator๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.

Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);

Optional<Dish> mostCaloriesDish = menu.stream().collect(maxBy(dishCaloriesComparator));

์ŠคํŠธ๋ฆผ์— ์žˆ๋Š” ๊ฐ์ฒด์˜ ์ˆซ์ž ํ•„๋“œ์˜ ํ•ฉ๊ณ„๋‚˜ ํ‰๊ท  ๋“ฑ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ฐ์‚ฐ์—๋„ ๋ฆฌ๋“€์‹ฑ ๊ธฐ๋Šฅ์ด ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค. ์ด๋Ÿฌํ•œ ์—ฐ์‚ฐ์„ ์š”์•ฝ ์—ฐ์‚ฐ์ด๋ผ ๋ถ€๋ฅธ๋‹ค.

6.2.2 ์š”์•ฝ ์—ฐ์‚ฐ

Collectors ํด๋ž˜์Šค๋Š” Collectors.summingInt๋ผ๋Š” ํŠน๋ณ„ํ•œ ์š”์•ฝ ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

summingInt๋Š” ๊ฐ์ฒด๋ฅผ int๋กœ ๋งคํ•‘ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์œผ๋ฉฐ, ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋œ ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด๋ฅผ int๋กœ ๋งคํ•‘ํ•œ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  summingInt๊ฐ€ collect ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌ๋˜๋ฉด ์š”์•ฝ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋‹ค์Œ์€ ๋ฉ”๋‰ด ๋ฆฌ์ŠคํŠธ์˜ ์ด ์นผ๋กœ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ์ฝ”๋“œ๋‹ค.

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

์ด๋Ÿฌํ•œ ๋‹จ์ˆœ ํ•ฉ๊ณ„ ์™ธ์— ํ‰๊ท ๊ฐ’ ๊ณ„์‚ฐ ๋“ฑ์˜ ์—ฐ์‚ฐ๋„ ์š”์•ฝ ๊ธฐ๋Šฅ์œผ๋กœ ์ œ๊ณต๋œ๋‹ค.

๋งŒ์•ฝ ๋‘๊ฐœ ์ด์ƒ์˜ ์—ฐ์‚ฐ์ด ํ•œ๋ฒˆ์— ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๋ฉด summarizingInt๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
// menuStatistics : IntSummaryStatistics{count=9, sum=4300, min=120, average=477.778, max=800}

6.2.3 ๋ฌธ์ž์—ด ์—ฐ๊ฒฐ

์ปฌ๋ ‰ํ„ฐ์— joining ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ๊ฐ์ฒด์— toString ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ถ”์ถœํ•œ ๋ชจ๋“  ๋ฌธ์ž์—ด์„ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด๋กœ ์—ฐ๊ฒฐํ•ด์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

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

์—ฐ๊ฒฐ๋œ ๋‘ ์š”์†Œ ๊ฐ„์— ๊ตฌ๋ถ„ ๋ฌธ์ž์—ด์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก ์˜ค๋ฒ„๋กœ๋“œ๋œ joining ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋„ ์žˆ๋‹ค.

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

6.2.4 ๋ฒ”์šฉ ๋ฆฌ๋“€์‹ฑ ์š”์•ฝ ์—ฐ์‚ฐ

์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋ชจ๋“  ์ปฌ๋ ‰ํ„ฐ๋Š” reducing ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋กœ๋„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์ฝ”๋“œ์ฒ˜๋Ÿผ reducing ๋ฉ”์„œ๋“œ๋กœ ๋งŒ๋“ค์–ด์ง„ ์ปฌ๋ ‰ํ„ฐ๋กœ๋„ ๋ฉ”๋‰ด์˜ ๋ชจ๋“  ์นผ๋กœ๋ฆฌ ํ•ฉ๊ฒŒ๋ฅผ ๊ฒŒ์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

int totalCalrories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j);

๋˜๋Š” ํ•œ ๊ฐœ์˜ ์ธ์ˆ˜๋ฅผ ๊ฐ€์ง„ reducing ๋ฒ„์ „์„ ์ด์šฉํ•ด์„œ ๊ฐ€์žฅ ์นผ๋กœ๋ฆฌ๊ฐ€ ๋†’์€ ์š”๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜๋„ ์žˆ๋‹ค.

Optional<Dish> mostCaloriesDish = menu.stream().collect(reducing(d1, d2)
  -> d1.getCalories() > d2.getCalories() ? d1 : d2));

์ปฌ๋ ‰์…˜ ํ”„๋ ˆ์ž„์›Œํฌ ์œ ์—ฐ์„ฑ : ๊ฐ™์€ ์—ฐ์‚ฐ๋„ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ „ ์˜ˆ์ œ์—์„œ ๋žŒ๋‹ค ํ‘œํ˜„์‹ ๋Œ€์‹  Integer ํด๋ž˜์Šค์˜ sum ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ์ข€ ๋” ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));

counting ์ปฌ๋ ‰ํ„ฐ๋„ ์„ธ ๊ฐœ์˜ ์ธ์ˆ˜๋ฅผ ๊ฐ–๋Š” reducing ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

public static <T> Collector<T, ?, Long> counting() {
  return reducing(0L, e -> 1L, Long::sum);
}

์ž์‹ ์˜ ์ƒํ™ฉ์— ๋งž๋Š” ์ตœ์ ์˜ ํ•ด๋ฒ• ์„ ํƒ

ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ํ•˜๋‚˜์˜ ์—ฐ์‚ฐ์„ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์ปฌ๋ ‰ํ„ฐ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ง์ ‘ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์— ๋น„ํ•ด ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€์ง€๋งŒ,

์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ ๊ฐ€๋Šฅ์„ฑ์„ ์ œ๊ณตํ•˜๋Š” ๋†’์€ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”์™€ ์ผ๋ฐ˜ํ™”๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.


6.3๊ทธ๋ฃนํ™”

์ž๋ฐ”8์˜ ํ•จ์ˆ˜ํ˜•์„ ์ด์šฉํ•˜๋ฉด ๊ฐ€๋…์„ฑ ์žˆ๋Š” ํ•œ ์ค„ ์ฝ”๋“œ๋กœ ๊ทธ๋ฃนํ™”๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

Map<Dish, Type, List<Dish>> dishsByType = menu.stream().collect(groupingBy(Dish::getType));
//dishsByType : {FISH=[prawns, salmon], OTHERS=[french fries, rice, pizza], MEAT[pork, beef, chicken]}

์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”๋ฆฌ์—์„œ Dish.Type๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ชจ๋“  ์š”๋ฆฌ๋ฅผ ์ถ”์ถœํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ groupbingBy ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌํ–ˆ๋‹ค.

์ด ํ•จ์ˆ˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ŠคํŠธ๋ฆผ์ด ๊ทธ๋ฃนํ™”๋˜๋ฏ€๋กœ ์ด๋ฅผ ๋ถ„๋ฅ˜ํ•จ์ˆ˜๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

๋‹จ์ˆœํ•œ ์†์„ฑ ์ ‘๊ทผ์ž ๋Œ€์‹  ๋” ๋ณต์žกํ•œ ๋ถ„๋ฅ˜ ๊ธฐ์ค€์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋Š” ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด 400 ์นผ๋กœ๋ฆฌ ์ดํ•˜๋ฅผ 'diet', 400~700์นผ๋กœ๋ฆฌ๋ฅผ 'normal', 700์นผ๋กœ๋ฆฌ ์ด์ƒ์„ 'fat' ์š”๋ฆฌ๋กœ ๋ถ„๋ฅ˜ํ•œ๋‹ค ๊ฐ€์ •ํ•ด๋ณด์ž.

public enum CaloricLevel { DIET, NORMAL, FAT }

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
  groupingBy(dish -> {
    if (dish.getCalories() <= 400 ) return CaloricLevel.DIET;
    else if (dish.getCalories() <= 700 ) return CaloricLevel.NORMAL;
    else return CaloricLevel.FAT;
  }));

6.3.1 ๊ทธ๋ฃนํ™”๋œ ์š”์†Œ ์กฐ์ž‘

์š”์†Œ๋ฅผ ๊ทธ๋ฃนํ™” ํ•œ ๋‹ค์Œ์—๋Š” ๊ฐ ๊ฒฐ๊ณผ ๊ทธ๋ฃน์˜ ์š”์†Œ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ์—ฐ์‚ฐ์ด ํ•„์š”ํ•˜๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด 500์นผ๋กœ๋ฆฌ๊ฐ€ ๋„˜๋Š” ์š”๋ฆฌ๋งŒ ํ•„ํ„ฐํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž. ๋‹ค์Œ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๊ทธ๋ฃนํ™”๋ฅผ ํ•˜๊ธฐ ์ „์— ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋กœ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•  ๊ฒƒ์ด๋‹ค.

Map<Dish, Type, List<Dish>> caloricDishesByType = menu.stream()
  .filter(dish -> dish.getCalories() > 500)
  .collect(groupingBy(Dish::getType));
//caloricDishesByType : {OTHER=[french fries, pizza], MEAT=[pork, beef]}

์œ„ ์ฝ”๋“œ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•„ํ„ฐ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ๋งŒ์กฑํ•˜๋Š” FISH ์ข…๋ฅ˜ ์š”๋ฆฌ๋Š” ์—†์œผ๋ฏ€๋กœ ๊ฒฐ๊ณผ ๋งต์—์„œ ํ•ด๋‹น ํ‚ค ์ž์ฒด๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.

ํ•„ํ„ฐ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ groupingBy ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ์˜ ์ธ์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

Map<Dish, Type, List<Dish>> caloricDishesByType = menu.stream()
  .collect(groupingBy(Dish::getType, filtering(dish -> getCalrories() > 500, toList())));
//caloricDishesByType : {OTHER=[french fries, pizza], MEAT=[pork, beef], FISH=[]}

filtering ๋ฉ”์„œ๋“œ๋Š” Collectors ํด๋ž˜์Šค์˜ ๋˜๋‹ค๋ฅธ ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋กœ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค. ์ด ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋กœ ๊ฐ ๊ทธ๋ฃน์˜ ์š”์†Œ์™€ ํ•„ํ„ฐ๋ง๋œ ์š”์†Œ๋ฅผ ์žฌ๊ทธ๋ฃนํ™”ํ•œ๋‹ค.

๊ทธ๋ฃนํ™”๋œ ํ•ญ๋ชฉ์„ ์กฐ์ž‘ํ•˜๋Š” ๋‹ค๋ฅธ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋Š” ๋งคํ•‘ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์š”์†Œ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์ด๋‹ค.

Map<Dish, Type, List<Sting>> dishNamesByTypes = menu.stream()
  .collect(groupingBy(Dish::Type, mapping(Dish::getName, toList())));

๊ฐ ๊ทธ๋ฃน์ด ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๋ผ๋ฉด flatMap ๋ณ€ํ™˜์„ ์‚ฌ์šฉํ•ด์„œ ์ถ”์ถœํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

Map<String, List<String>> dishTags = new HashMap<>();
dishTag.push("pork", asList("greasy", "salty"));
dishTag.push("beef", asList("salty", "roasted"));
dishTag.push("chicken", asList("fried", "crisp"));
dishTag.push("rice", asList("light", "natural"));

Map<Dish.Type, Set<String>> dishNamesByType = menu.stream()
  .collect(groupingBy(Dish::getType,
    flatMapping(dish -> dishTags.get(dish.getName()).stream(),
    toSet())));

6.3.2 ๋‹ค์ˆ˜์ค€ ๊ทธ๋ฃนํ™”

๋‘ ์ธ์ˆ˜๋ฅผ ๋ฐ›๋Š” ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ Collectors.groupingBy๋ฅผ ์ด์šฉํ•ด์„œ ํ•ญ๋ชฉ์„ ๋‹ค์ˆ˜์ค€์œผ๋กœ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

Map<Dish.Type, Map<CalricLevel, 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;
    })
  )
};
//dishesByTypeCaloricLevel : 
//{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]}, FISH={DIET=[prawns], NORMAL=[salmon]}, OTHER={...}}

์™ธ๋ถ€ ๋งต์€ ์ฒซ ๋ฒˆ์งธ ์ˆ˜์ค€์˜ ๋ถ„๋ฅ˜ํ•จ์ˆ˜์—์„œ ๋ถ„๋ฅ˜ํ•œ ํ‚ค๊ฐ’ 'fish, meat, other'๋ฅผ ๊ฐ€์ง€๋ฉฐ, ๋‚ด๋ถ€ ๋งต์€ ๋‘ ๋ฒˆ์งธ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜์˜ ํ‚ค๊ฐ’ 'normal, diet, fat'์„ ๊ฐ€์ง„๋‹ค.

๋ณดํ†ต groupingBy์˜ ์—ฐ์‚ฐ์„ '๋ฒ„ํ‚ท(๋ฌผ๊ฑด์„ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ์–‘๋™์ด)' ๊ฐœ๋…์œผ๋กœ ์ƒ๊ฐํ•˜๋ฉด ์‰ฝ๋‹ค.

์ฒซ ๋ฒˆ์งธ groupingBy๋Š” ๊ฐ ํ‚ค์˜ ๋ฒ„ํ‚ท์„ ๋งŒ๋“ ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ค€๋น„๋œ ๊ฐ๊ฐ์˜ ๋ฒ„ํ‚ท์„ ์„œ๋ธŒ์ŠคํŠธ๋ฆผ ์ปฌ๋ ‰ํ„ฐ๋กœ ์ฑ„์›Œ๊ฐ€๊ธฐ๋ฅผ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ n์ˆ˜์ค€ ๊ทธ๋ฃนํ™”๋ฅผ ๋‹ฌ์„ฑํ•œ๋‹ค.

6.3.3 ์„œ๋ธŒ๊ทธ๋ฃน์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘

groupingBy ๋ฉ”์„œ๋“œ์˜ ๋‘๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋ฐ›๋Š” ์ปฌ๋ ‰ํ„ฐ์˜ ํ˜•์‹์€ ์ œํ•œ์ด ์—†๋‹ค.

๋ถ„๋ฅ˜ ํ•จ์ˆ˜ ํ•œ๊ฐœ์˜ ์ธ์ˆ˜๋ฅผ ๊ฐ–๋Š” groupingBy(f)๋Š” groupingBy(f, toList())์˜ ์ถ•์•ฝํ˜•์ผ ๋ฟ์ด๋ฉฐ, ๋‹ค์–‘ํ•œ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));
//{MEAT=3, FISH=2, OTHER=4}

Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream()
  .collect(groupingBy(Dish::getType, maxBy(CompaingInt(Dish::getCalories))));
//{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}

์ปฌ๋ ‰ํ„ฐ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค๋ฅธ ํ˜•์‹์— ์ ์šฉํ•˜๊ธฐ

๋งˆ์ง€๋ง‰ ๊ทธ๋ฃนํ™” ์—ฐ์‚ฐ์—์„œ ๋งต์˜ ๋ชจ๋“  ๊ฐ’์„ Optional๋กœ ๊ฐ์Œ€ ํ•„์š”๊ฐ€ ์—†์œผ๋ฏ€๋กœ Optional์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

Collectors.collectingAndThen ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋กœ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค๋ฅธ ํ˜•์‹์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream()
  .collect(groupingBy(Dish::getType,
    collectingAndThen(maxBy(CompaingInt(Dish::getCalories)), Optional::get)));
//{FISH=salmon, OTHER=pizza, MEAT=pork}

groupingBy์™€ ํ•จ๊ผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์ปฌ๋ ‰ํ„ฐ ์˜ˆ์ œ

์ผ๋ฐ˜์ ์œผ๋กœ ์ŠคํŠธ๋ฆผ์—์„œ ๊ฐ™์€ ๊ทธ๋ฃน์œผ๋กœ ๋ถ„๋ฅ˜๋œ ๋ชจ๋“  ์š”์†Œ์— ๋ฆฌ๋“€์‹ฑ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ์—๋Š” ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ groupingBy์— ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•œ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Map<Dish.Type, Integer> totalCaloriesByType = menu.stream()
  .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));

์ด ์™ธ์—๋„ mapping ๋ฉ”์„œ๋“œ๋กœ ๋งŒ๋“ค์–ด์ง„ ์ปฌ๋ ‰ํ„ฐ๋„ groupingBy์™€ ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค.

mapping์€ ์ž…๋ ฅ ์š”์†Œ๋ฅผ ๋ˆ„์ ํ•˜๊ธฐ ์ „์— ๋งคํ•‘ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•ด์„œ ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๊ฐ์ฒด๋ฅผ ์ฃผ์–ด์ง„ ํ˜•์‹์˜ ์ปฌ๋ ‰ํ„ฐ์— ๋งž๊ฒŒ ๋ณ€ํ™˜ํ•œ๋‹ค.

Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream()
  .collect(groupingBy(Dish::getType, mapping(dish -> {
    if (dish.getCalories() <= 400) return caloricLevel.DIET;
    else if (dish.getCalories() <= 700) return caloricLevel.NORMAL;
    else return caloricLevel.FAT;
  }, toSet() )));
  //{OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], FISH=[DIET, NORMAL]}

6.4 ๋ถ„ํ• 

๋ถ„ํ• ์€ ๋ถ„ํ•  ํ•จ์ˆ˜๋ผ ๋ถˆ๋ฆฌ๋Š” ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋Š” ํŠน์ˆ˜ํ•œ ๊ทธ๋ฃนํ™” ๊ธฐ๋Šฅ์ด๋‹ค.

๋ถ„ํ•  ํ•จ์ˆ˜๋Š” ๋ถˆ๋ฆฌ์–ธ์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋งต์˜ ํ‚ค ํ˜•์‹์€ Boolean์ด๋ฉฐ, ์ฐธ ๋˜๋Š” ๊ฑฐ์ง“์„ ๊ฐ–๋Š” ๋‘ ๊ฐœ์˜ ๊ทธ๋ฃน์œผ๋กœ ๋ถ„๋ฅ˜๋œ๋‹ค.

Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));
// {false=[pork, beef, chicken, prawns, salmon],
// true=[french fires, rice, season fruit, pizza]}

true ๊ฐ’์˜ ํ‚ค๋กœ ๋งต์—์„œ ๋ชจ๋“  ์ฑ„์‹ ์š”๋ฆฌ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

List<Dish> vegetarianDishes = partitionedMenu.get(true); //์ฑ„์‹์ธ ์š”๋ฆฌ

๋ฉ”๋‰ด ๋ฆฌ์ŠคํŠธ๋กœ ์ƒ์„ฑํ•œ ์ŠคํŠธ๋ฆผ์„ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋กœ ํ•„ํ„ฐ๋งํ•ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

List<Dish> vegetarianDishes = menu.stream().filter(Dish::isVegetarian).collect(toList());

6.4.1 ๋ถ„ํ• ์˜ ์žฅ์ 

๋ถ„ํ•  ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฐธ, ๊ฑฐ์ง“ ๋‘ ๊ฐ€์ง€ ์š”์†Œ์˜ ์ŠคํŠธ๋ฆผ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ชจ๋‘ ์œ ์ง€ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ๋ถ„ํ•  ํ•จ์ˆ˜์˜ ์žฅ์ ์ด๋‹ค.

๋˜ํ•œ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฒ„๋กœ๋“œ๋œ ๋ฒ„์ „์˜ partitioningBy ๋ฉ”์„œ๋“œ๋„ ์žˆ๋‹ค.

Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(
  Dish::isVegetarian, groupingBy(Dish::getType));
// {false=FISH=[prawns, salmon], MEAT=[pork, beef, chicken],
// true=OTHER=[french fires, rice, season fruit, pizza]}

์ด์ „ ์ฝ”๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ฑ„์‹ ์š”๋ฆฌ์™€ ์ฑ„์‹์ด ์•„๋‹Œ ์š”๋ฆฌ์˜ ๊ฐ๊ฐ์˜ ๊ทธ๋ฃน์—์„œ ๊ฐ€์žฅ ์นผ๋กœ๋ฆฌ๊ฐ€ ๋†’์€ ์š”๋ฆฌ๋„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(
  Dish::isVegetarian, collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get));
// {false=pork, true=pizza}

6.4.2 ์ˆซ์ž๋ฅผ ์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋กœ ๋ถ„ํ• ํ•˜๊ธฐ

์ •์ˆ˜ n์„ ์ธ์ˆ˜๋กœ ๋ฐ›์•„์„œ 2์—์„œ n๊นŒ์ง€์˜ ์ž์—ฐ์ˆ˜๋ฅผ ์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋กœ ๋‚˜๋ˆ„๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌํ˜„ํ•˜์ž.

๋จผ์ € ์ฃผ์–ด์ง„ ์ˆ˜๊ฐ€ ์†Œ์ˆ˜์ธ์ง€ ์•„๋‹Œ์ง€ ํŒ๋ณ„ํ•˜๋Š” ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

public boolean isPrime(int candidate) {
  return IntStream.range(2, candidate).noneMatch(i -> candidate % i == 0);
  //์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ์ •์ˆ˜๋กœ candidate๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์œผ๋ฉด ์ฐธ์„ ๋ฐ˜ํ™˜
}

๋‹ค์Œ์ฒ˜๋Ÿผ ์†Œ์ˆ˜์˜ ๋Œ€์ƒ์„ ์ฃผ์–ด์ง„ ์ˆ˜์˜ ์ œ๊ณฑ๊ทผ ์ดํ•˜์˜ ์ˆ˜๋กœ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.

public boolean isPrime(int candidate) {
  int candidateRoot = (int) Math.sqrt((double)candidate);
  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
  //์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ์ •์ˆ˜๋กœ candidate๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์œผ๋ฉด ์ฐธ์„ ๋ฐ˜ํ™˜
}

์ด์ œ n๊ฐœ์˜ ์ˆซ์ž๋ฅผ ํฌํ•จํ•˜๋Š” ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“  ๋‹ค์Œ, isPrime ๋ฉ”์„œ๋“œ๋ฅผ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋กœ ์ด์šฉํ•˜๊ณ  partitioningBy ์ปฌ๋ ‰ํ„ฐ๋กœ ๋ฆฌ๋“€์Šคํ•ด์„œ ์ˆซ์ž๋ฅผ ์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ๋‹ค.

public Map<Boolean, List<Integer>> partitionPrimes(int n) {
  return IntStream.rangeClosed(2, n).boxed().collect(partitioningBy(candidate -> isPrime(candidate)));
}

6.5 Collector ์ธํ„ฐํŽ˜์ด์Šค

Collector ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ(์ฆ‰, ์ปฌ๋ ‰ํ„ฐ)์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ• ์ง€ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ง‘ํ•ฉ์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

Collector ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ดํŽด๋ณด๊ธฐ ์ „์— 6์žฅ์„ ์‹œ์ž‘ํ•˜๋ฉด์„œ ์‚ดํŽด๋ณธ ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ์ธ toList๋ฅผ ์ž์„ธํžˆ ํ™•์ธํ•˜์ž.

๋‹ค์Œ ์ฝ”๋“œ๋Š” Collector ์ธํ„ฐํŽ˜์ด์Šค์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ ๋‹ค์„ฏ ๊ฐœ์˜ ๋ฉ”์„œ๋“œ ์ •์˜๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

public interface Collector<T, A, R> {
  Supplier<A> supplier();
  BiConsumer<A, T> accumulator();
  Function<A, R> finisher();
  BinaryOperator<A> combiner();
  Set<Characteristics> characteristics();
}

์œ„ ์ฝ”๋“œ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • T๋Š” ์ˆ˜์ง‘๋  ์ŠคํŠธ๋ฆผ ํ•ญ๋ชฉ์˜ ์ œ๋„ค๋ฆญ ํ˜•์‹์ด๋‹ค.

  • A๋Š” ๋ˆ„์ ์ž. ์ฆ‰ ์ˆ˜์ง‘ ๊ณผ์ •์—์„œ ์ค‘๊ฐ„ ๊ฒฐ๊ณผ๋ฅผ ๋ˆ„์ ํ•˜๋Š” ๊ฐ์ฒด์˜ ํ˜•์‹์ด๋‹ค.

  • R์€ ์ˆ˜์ง‘ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ ๊ฐ์ฒด์˜ ํ˜•์‹(ํ•ญ์ƒ ๊ทธ๋Ÿฐ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ ๋Œ€๊ฒŒ ์ปฌ๋ ‰์…˜ ํ˜•์‹)์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด Stream<T>์˜ ๋ชจ๋“  ์š”์†Œ๋ฅผ List<T>๋กœ ์ˆ˜์ง‘ํ•˜๋Š” ToListCollector<T>๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class ToListCollector<T> implements Collector<T, List<T>, List<T>>

6.5.1 Collector ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์„œ๋“œ ์‚ดํŽด๋ณด๊ธฐ

supplier ๋ฉ”์„œ๋“œ : ์ƒˆ๋กœ์šด ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๋งŒ๋“ค๊ธฐ

supplier ๋ฉ”์„œ๋“œ๋Š” ๋นˆ ๊ฒฐ๊ณผ๋กœ ์ด๋ฃจ์–ด์ง„ Supplier๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ์ˆ˜์ง‘ ๊ณผ์ •์—์„œ ๋นˆ ๋ˆ„์ ์ž ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋Š” ํ•จ์ˆ˜๋‹ค.

ToListCollector์ฒ˜๋Ÿผ ๋ˆ„์ ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปฌ๋ ‰ํ„ฐ์—์„œ๋Š” ๋นˆ ๋ˆ„์ ์ž๊ฐ€ ๋น„์–ด์žˆ๋Š” ์ŠคํŠธ๋ฆผ์˜ ์ˆ˜์ง‘ ๊ณผ์ •์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

public Supplier<List<T>> supplier() {
  return() -> new ArrayList<T>();
}

//์ƒ์„ฑ์ž ์ฐธ์กฐ ๋ฐฉ์‹์œผ๋กœ ์ „๋‹ฌ๋„ ๊ฐ€๋Šฅ
public Supplier<List<T>> supplier() {
  return ArrayList::new;
}

accumlator ๋ฉ”์„œ๋“œ : ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ์— ์š”์†Œ ์ถ”๊ฐ€ํ•˜๊ธฐ

accumlator ๋ฉ”์„œ๋“œ๋Š” ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ŠคํŠธ๋ฆผ์—์„œ n๋ฒˆ์งธ ์š”์†Œ๋ฅผ ํƒ์ƒ‰ํ•  ๋•Œ ๋‘ ์ธ์ˆ˜, ์ฆ‰ ๋ˆ„์ ์ž(n-1๋ฒˆ์งธ ํ•ญ๋ชฉ๊นŒ์ง€ ์ˆ˜์ง‘ํ•œ ์ƒํƒœ)์™€ n๋ฒˆ์งธ ์š”์†Œ๋ฅผ ํ•จ์ˆ˜์— ์ ์šฉํ•œ๋‹ค.

ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์€ void, ์ฆ‰ ์š”์†Œ๋ฅผ ํƒ์ƒ‰ํ•˜๋ฉด์„œ ์ ์šฉํ•˜๋Š” ํ•จ์ˆ˜์— ์˜ํ•ด ๋ˆ„์ ์ž ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋ฏ€๋กœ ๋ˆ„์ ์ž๊ฐ€ ์–ด๋–ค ๊ฐ’์ผ์ง€ ๋‹จ์ •ํ•  ์ˆ˜ ์—†๋‹ค.

public BiConsumer<List<T>, T> acuumulator() {
  return (list, item) -> list.add(item);
}

//๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๊ฐ„๊ฒฐํ•ด์ง„๋‹ค.
public BiConsumer<List<T>, T> accumulator() {
  return List::add;
}

finisher ๋ฉ”์„œ๋“œ : ์ตœ์ข… ๋ณ€ํ™˜๊ฐ’์„ ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ๋กœ ์ ์šฉํ•˜๊ธฐ

finisher ๋ฉ”์„œ๋“œ๋Š” ์ŠคํŠธ๋ฆผ ํƒ์ƒ‰์„ ๋๋‚ด๊ณ  ๋ˆ„์ ์ž ๊ฐ์ฒด๋ฅผ ์ตœ์ข… ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด์„œ ๋ˆ„์  ๊ณผ์ •์„ ๋๋‚ผ ๋•Œ ํ˜ธ์ถœํ•  ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.

public Function<List<T> List<T>> finisher() {
  return Function.identity();
}

combiner ๋ฉ”์„œ๋“œ : ๋‘ ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๋ณ‘ํ•ฉ

combiner๋Š” ์ŠคํŠธ๋ฆผ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋ธŒํŒŒํŠธ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋ˆ„์ ์ž๊ฐ€ ์ด ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์ •์˜ํ•œ๋‹ค.

toList์˜ combiner๋Š” ๋น„๊ต์  ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ŠคํŠธ๋ฆผ์˜ ๋‘ ๋ฒˆ์งธ ์„œ๋ธŒ ํŒŒํŠธ์—์„œ ์ˆ˜์ง‘ํ•œ ํ•ญ๋ชฉ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์„œ๋ธŒํŒŒํŠธ ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์˜ ๋’ค์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

public BinaryOperator<List<T>> combiner() {
  return (list1, list2) -> {
    liat.addAll(list2);
    return list1;
  }
}

์ด ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์˜ ๋ฆฌ๋“€์‹ฑ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ณ‘๋ ฌ ๋ฆฌ๋“€์‹ฑ ์ˆ˜ํ–‰ ๊ณผ์ •

  • ์ŠคํŠธ๋ฆผ์„ ๋ถ„ํ• ํ•ด์•ผ ํ•˜๋Š”์ง€ ์ •์˜ํ•˜๋Š” ์กฐ๊ฑด์ด ๊ฑฐ์ง“์œผ๋กœ ๋ฐ”๋€Œ๊ธฐ ์ „๊นŒ์ง€ ์›๋ž˜ ์ŠคํŠธ๋ฆผ์„ ์žฌ๊ท€์ ์œผ๋กœ ๋ถ„ํ• ํ•œ๋‹ค.

  • ๋ชจ๋“  ์„œ๋ธŒ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”์†Œ์— ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ ์šฉํ•ด์„œ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • ์ปฌ๋ ‰ํ„ฐ์˜ combiner ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋ชจ๋“  ๋ถ€๋ถ„๊ฒฐ๊ณผ๋ฅผ ์Œ์œผ๋กœ ํ•ฉ์นœ๋‹ค.

Characteristics ๋ฉ”์„œ๋“œ

characteristics ๋ฉ”์„œ๋“œ๋Š” ์ปฌ๋ ‰ํ„ฐ์˜ ์—ฐ์‚ฐ์„ ์ •์˜ํ•˜๋Š” Characteristics ํ˜•์‹์˜ ๋ถˆ๋ณ€ ์ง‘ํ•ฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Characteristics๋Š” ์ŠคํŠธ๋ฆผ์„ ๋ณ‘๋ ฌ๋กœ ๋ฆฌ๋“€์Šค ํ• ์ง€์™€ ๋ณ‘๋ ฌ๋กœ ๋ฆฌ๋“€์Šคํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ตœ์ ํ•˜๋ฅผ ์„ ํƒํ•ด์•ผํ• ์ง€ ํžŒํŠธ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

Characteristics์˜ ํ•ญ๋ชฉ

  • UNORDERED : ๋ฆฌ๋“€์‹ฑ ๊ฒฐ๊ณผ๋Š” ์ŠคํŠธ๋ฆผ ์š”์†Œ์˜ ๋ฐฉ๋ฌธ ์ˆœ์„œ๋‚˜ ๋ˆ„์  ์ˆœ์„œ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.

  • CONCURRENT : ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ์—์„œ accumulator ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด ์ปฌ๋ ‰ํ„ฐ๋Š” ์ŠคํŠธ๋ฆผ์˜ ๋ณ‘๋ ฌ ๋ฆฌ๋“€์‹ฑ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ปฌ๋ ‰ํ„ฐ์˜ ํ”Œ๋ž˜๊ทธ์— UNORDERED๋ฅผ ํ•จ๊ป˜ ์„ค์ •ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์ •๋ ฌ๋˜์–ด์žˆ์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋งŒ ๋ณ‘๋ ฌ ๋ฆฌ๋“€์‹ฑ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • IDENTITY_FINISH : finisher ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋Š” ๋‹จ์ˆœํžˆ identity๋ฅผ ์ ์šฉํ•  ๋ฟ์ด๋ฏ€๋กœ ์ด๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฆฌ๋“€์‹ฑ ๊ณผ์ •์˜ ์ตœ์ข… ๊ฒฐ๊ณผ๋กœ ๋ˆ„์ ์ž ๊ฐ์ฒด๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ˆ„์ ์ž A๋ฅผ ๊ฒฐ๊ณผ R๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜•๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

6.5.2 ์‘์šฉํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋‹ค์„ฏ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ž์‹ ๋งŒ์˜ ์ปค์Šคํ…€ toListCollector๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class ToListCollector<T> implements Collect<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) -> {
      liat.addAll(list2);
      return list1;
    }
  }
    
  @Override
  public Set<Characteristics> characteristics() {
    return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
  }
}

์ปฌ๋ ‰ํ„ฐ ๊ตฌํ˜„์„ ๋งŒ๋“ค์ง€ ์•Š๊ณ ๋„ ์ปค์Šคํ…€ ์ˆ˜์ง‘ ์ˆ˜ํ–‰ํ•˜๊ธฐ

IDENTITY_FINISH ์ˆ˜์ง‘ ์—ฐ์‚ฐ์—์„œ๋Š” Collector ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์™„์ „ํžˆ ์ƒˆ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

Stream์€ ์„ธ ํ•จ์ˆ˜(๋ฐœํ–‰, ๋ˆ„์ , ํ•ฉ์นจ)๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” collect ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋กœ๋“œํ•˜์—ฌ ๊ฐ๊ฐ์˜ ๋ฉ”์„œ๋“œ๋Š” Collector ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

List<Dish> dishes = menuStream.collect(
  ArrayList::new, //๋ฐœํ–‰
  List::add,  //๋ˆ„์ 
  List:addAll); //ํ•ฉ์นจ

6.6 ์ปค์Šคํ…€ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ

์ด์ „ ์˜ˆ์ œ์—์„œ n๊นŒ์ง€์˜ ์ž์—ฐ์ˆ˜๋ฅผ ์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋กœ ๋ถ„ํ• ํ•˜๋Š” ์ปค์Šคํ…€ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

public Map<Boolean, List<Integer>> partitionPrimes(int n) {
  return IntStream.rangeClosed(2, n).boxed().collect(partitioningBy(candidate -> isPrime(candidate)));
}

public boolean isPrime(int candidate) {
  int candidateRoot = (int) Math.sqrt((double)candidate);
  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}

์ปค์Šคํ…€ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์ด์šฉํ•ด์„œ ์„ฑ๋Šฅ์„ ๋” ๊ฐœ์„ ํ•ด๋ณด์ž.

6.6.1 ์†Œ์ˆ˜๋กœ๋งŒ ๋‚˜๋ˆ„๊ธฐ

์šฐ์„  ์†Œ์ˆ˜๋กœ ๋‚˜๋ˆ„์–ด๋–จ์–ด์ง€๋Š”์ง€ ํ™•์ธํ•ด์„œ ๋Œ€์ƒ์˜ ๋ฒ”์œ„๋ฅผ ์ขํž ์ˆ˜ ์žˆ๋‹ค.

์ œ์ˆ˜๊ฐ€ ์†Œ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ฉด ์†Œ์šฉ์—†์œผ๋ฏ€๋กœ ์ œ์ˆ˜๋ฅผ ํ˜„์žฌ ์ˆซ์ž ์ดํ•˜์—์„œ ๋ฐœ๊ฒฌํ•œ ์†Œ์ˆ˜๋กœ ์ œํ•œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฃผ์–ด์ง„ ์ˆซ์ž๊ฐ€ ์†Œ์ˆ˜์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌํ•œ ์†Œ์ˆ˜ ๋ฆฌ์ŠคํŠธ์— ์ ‘๊ทผํ•ด์•ผํ•œ๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ์‚ดํŽด๋ณธ ์ปฌ๋ ‰ํ„ฐ๋กœ๋Š” ์ปฌ๋ ‰ํ„ฐ ์ˆ˜์ง‘ ๊ณผ์ •์—์„œ ๋ถ€๋ถ„ ๊ฒฐ๊ณผ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์ง€๋งŒ, ์ปค์Šคํ…€ ์ปฌ๋ ‰ํ„ฐ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

public boolean isPrime(List<Integer> primes, int candidate) {
  return primes.stream().noneMatch(i -> candidate % i == 0);
}

์ด๋ฒˆ์—๋„ ๋Œ€์ƒ ์ˆซ์ž์˜ ์ œ๊ณฑ๊ทผ๋ณด๋‹ค ์ž‘์€ ์†Œ์ˆ˜๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ตœ์ ํ™”ํ•ด์•ผํ•œ๋‹ค.

filter(p -> p <= candidtaRoot)๋ฅผ ์ด์šฉํ•ด์„œ ๋Œ€์ƒ์˜ ๋ฃจํŠธ๋ณด๋‹ค ์ž‘์€ ์†Œ์ˆ˜๋ฅผ ํ•„ํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, filter๋Š” ์ „์ฒด ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•œ ๋‹ค์Œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.

๋Œ€์ƒ์˜ ์ œ๊ณฑ๋ณด๋‹ค ํฐ ์†Œ์ˆ˜๋ฅผ ์ฐพ์œผ๋ฉด ๊ฒ€์‚ฌ๋ฅผ ์ค‘๋‹จํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” talkWhile ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

public boolean isPrime(List<Integer> primes, int candidate) {
  int candidateRoot = (int) Math.sqrt((double)candidate);
  return primes.stream()
    .talkWhile(i -> i <= candidateRoot);
    .noneMatch(i -> candidate % i == 0);
}

talkWhile ๋ฉ”์„œ๋“œ๋Š” ์ž๋ฐ”9๋ถ€ํ„ฐ ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์ž๋ฐ” 8์—์„œ๋Š” ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.

public static <A> List<A> talkWhile(List<A> list, Predicate<A> p) {
  int i = 0;
  for (A item : list) {
    if(!p.test(item)) {
      return list.subList(0, i); //ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ์ด์ „ ํ•˜์œ„ํ•ญ๋ชฉ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜
    }
    i++;
  }
  return list;
}

๊ฒŒ์œผ๋ฅธ ๋ฒ„์ „์˜ ์ŠคํŠธ๋ฆผ API์™€ ๋‹ฌ๋ฆฌ ์ง์ ‘ ๊ตฌํ˜„ํ•œ talkWhile ๋ฉ”์„œ๋“œ๋Š” ์ ๊ทน์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

์ƒˆ๋กœ์šด isPrime ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์œผ๋‹ˆ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ปค์Šคํ…€ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜์ž. Collector ํด๋ž˜์Šค๋ฅผ ์„ ์–ธํ•˜๊ณ  Collector ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์š”๊ตฌํ•˜๋Š” ๋ฉ”์„œ๋“œ ๋‹ค์„ฏ ๊ฐœ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.

1๋‹จ๊ณ„ : Collector ํด๋ž˜์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ •์˜

public interface Collector<T, A, R>

T๋Š” ์ŠคํŠธ๋ฆผ ์š”์†Œ์˜ ํ˜•์‹, A๋Š” ์ค‘๊ฐ„ ๊ฒฐ๊ณผ๋ฅผ ๋ˆ„์ ํ•˜๋Š” ๊ฐ์ฒด์˜ ํ˜•์‹, R์€ collect ์—ฐ์‚ฐ์˜ ์ตœ์ข… ๊ฒฐ๊ณผ ํ˜•์‹์„ ์˜๋ฏธํ•œ๋‹ค.

์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋ฅผ ์ฐธ๊ณผ ๊ฑฐ์ง“์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•ด ์ •์ˆ˜๋กœ ์ด๋ฃจ์–ด์ง„ ์ŠคํŠธ๋ฆผ์—์„œ ๋ˆ„์ ์ž์™€ ์ตœ์ข… ๊ฒฐ๊ณผ ํ˜•์‹์ด Map<Boolean, List<Integer>>์ธ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

public class PrimeNumbersCollector
  implements Collect<Integer,
  Map<Boolean, List<Integer>>,
  Map<Boolean, List<Integer>>>

2๋‹จ๊ณ„ : ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ ๊ตฌํ˜„

๋จผ์ € supplier ๋ฉ”์„œ๋“œ๋กœ ๋ˆ„์ ์ž๋ฅผ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.

public Supplier<Map<Boolean, List<Integer>>> supplier() {
  return () -> new HashMap<Boolean, List<Integer>>() {{
    put(true, new ArrayList<Integer>());
    put(false, new ArrayList<Integer>());
  }};
}

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๋ˆ„์ ์ž๋กœ ๋งŒ๋“ค ๋งต์„ ๋งŒ๋“ค๋ฉด์„œ true, false ํ‚ค์™€ ๋นˆ ๋ฆฌ์ŠคํŠธ๋กœ ์ดˆ๊ธฐํ™”ํ–ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์ŠคํŠธ๋ฆผ ์š”์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ์ˆ˜์ง‘ํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” accumulator ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค.

์ด์ œ ์–ธ์ œ๋“ ์ง€ ์›ํ•  ๋•Œ ์ˆ˜์ง‘ ๊ณผ์ •์˜ ์ค‘๊ฐ„ ๊ฒฐ๊ณผ, ์ฆ‰ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌํ•œ ์†Œ์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š” ๋ˆ„์ ์ž์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

public BiConsumer<Map<Boolean, List<Integer>>, Integer> accumulator() {
  return (Map<Boolean, List<Integer>> acc, Integer candidate) -> {
    acc.get( isPrime(acc.get(true), candidate) ) //isPrime ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ์†Œ์ˆ˜/๋น„์†Œ์ˆ˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.
      .add(candidate); //candidate๋ฅผ ์•Œ๋งž์€ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•œ๋‹ค.
  };
}

์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌํ•œ ์†Œ์ˆ˜ ๋ฆฌ์ŠคํŠธ acc.get(true) ์™€ ์†Œ์ˆ˜ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” candidate๋ฅผ ์ธ์ˆ˜๋กœ isPrime ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์•Œ๋งž์€ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

3๋‹จ๊ณ„ : ๋ณ‘๋ ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ปฌ๋ ‰ํ„ฐ ๋งŒ๋“ค๊ธฐ(๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด)

์ด๋ฒˆ์—๋Š” ๋ณ‘๋ ฌ ์ˆ˜์ง‘ ๊ณผ์ •์—์„œ ๋‘ ๋ถ€๋ถ„ ๋ˆ„์ ์ž๋ฅผ ํ•ฉ์น  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค. ์˜ˆ์ œ์—์„œ๋Š” ๋‘ ๋ฒˆ์งธ ๋งต์˜ ์†Œ์ˆ˜ ๋ฆฌ์ŠคํŠธ์™€ ๋น„์†Œ์ˆ˜ ๋ฆฌ์ŠคํŠธ์˜ ๋ชจ๋“  ์ˆ˜๋ฅผ ์ฒซ ๋ฒˆ์งธ ๋งต์— ์ถ”๊ฐ€ํ•˜๋Š” ์—ฐ์‚ฐ์ด๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค.

public BinaryOperator<Map<Boolean, List<Integer>>> combiner() {
  return (Map<Boolean, List<Integer>> map1, Map<Boolean, List<Integer>> map2) -> {
    map1.get(true).addAll(map2.get(true));
    map1.get(false).addAll(map2.get(false));
  };
}

์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ž์ฒด๊ฐ€ ์ˆœ์ฐจ์ ์ด์–ด์„œ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์‹ค์ œ๋กœ ๋ณ‘๋ ฌ๋กœ ์‚ฌ์šฉํ•  ์ˆœ ์—†์œผ๋ฏ€๋กœ combiner ๋ฉ”์„œ๋“œ๋Š” ๋นˆ ๊ตฌํ˜„์œผ๋กœ ๋‚จ๊ฒจ๋‘๊ฑฐ๋‚˜ exception์„ ๋˜์ง€๋„๋ก ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

4๋‹จ๊ณ„ : finisher ๋ฉ”์„œ๋“œ์™€ ์ปฌ๋ ‰ํ„ฐ์˜ characteristics ๋ฉ”์„œ๋“œ

accumulator์˜ ํ˜•์‹์€ ์ปฌ๋ ‰ํ„ฐ ๊ฒฐ๊ณผ ํ˜•์‹๊ณผ ๊ฐ™์œผ๋ฏ€๋กœ ๋ณ€ํ™˜ ๊ณผ์ •์€ ํ•„์š”์—†๋‹ค. ๋”ฐ๋ผ์„œ ํ•ญ๋“ฑ ํ•จ์ˆ˜ identity๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก finisher ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

public Function<Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> finisher() {
  return Function.identity();
}

6.6.2 ์ปฌ๋ ‰ํ„ฐ ์„ฑ๋Šฅ ๋น„๊ต

๊ฐ„๋‹จํ•œ ํ•˜๋‹ˆ์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ปฌ๋ ‰ํ„ฐ ์„ฑ๋Šฅ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

public class CollectorHarness {
  public static void main(String[] args) {
    long fastest = Long.MAX_VALUE;
    for (int i = 0; i < 10; i++) {
      long start = System.nanoTime();
      partitionPrimes(1_000_000); //๋ฐฑ๋งŒ ๊ฐœ์˜ ์ˆซ์ž๋ฅผ ์†Œ์ˆ˜์™€ ๋น„์†Œ์ˆ˜๋กœ ๋ถ„ํ• 
      long duration = (System.nanoTime() - start) / 1_000_000;
      if(duration < fastest) fastest = duration;
    }
    System.out.println"FASTEST EXECUTION DONE IN " + fastest + " msecs");
  }

collect ๋ฉ”์„œ๋“œ๋กœ PrimeNumbersCollector์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์„ธ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์„œ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

public Map<Booelan, List<Integer>> partitionPrimesWithCustomCollector(int n) {
  IntStream.rangeClosed(2, n).boxed().collect(
    () -> new HashMap<Boolean, List<Integer>>() {{ // ๋ฐœํ–‰
      put(true, new ArrayList<Integer>());
      put(false, new ArrayList<Integer>());
    }},
    (acc, candidate) -> { // ๋ˆ„์ 
      acc.get( isPrime(acc.get(true), candidate) )
        .add(candidate);
    },
    (map1, map2) -> { // ํ•ฉ์นจ
      map1.get(true).addAll(map2.get(true));
      map1.get(false).addAll(map2.get(false));
    });
}

Last updated