Part 2-2 ํจ์ํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
Part 2-2 ํจ์ํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ด๋ จ
6์ฅ - ์คํธ๋ฆผ์ผ๋ก ๋ฐ์ดํฐ ์์ง
4์ฅ๊ณผ 5์ฅ์์๋ ์คํธ๋ฆผ์์ ์ต์ข
์ฐ์ฐ collect
๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ํ์ธํ๋ค. ํ์ง๋ง toList๋ก ์คํธ๋ฆผ ์์๋ฅผ ํญ์ ๋ฆฌ์คํธ๋ก๋ง ๋ณํํ๋ค. ์ด ์ฅ์์๋ reduce
๊ฐ ๊ทธ๋ฌ๋ ๊ฒ์ฒ๋ผ collect
์ญ์ ๋ค์ํ ์์ ๋์ ๋ฐฉ์์ ์ธ์๋ก ๋ฐ์์ ์คํธ๋ฆผ์ ์ต์ข
๊ฒฐ๊ณผ๋ก ๋์ถํ๋ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ํํ ์ ์์์ ์ค๋ช
ํ๋ค.
// ํตํ๋ณ๋ก ํธ๋์ญ์
์ ๊ทธ๋ฃนํํ ์ฝ๋ - ๋ช
๋ นํ ๋ฒ์
Map<Currency, List<Transaction>> transactionByCurrencies = new HashMap<>();
for (Transaction transaction : transactions) {
Currency currency = transaction.getCurrency();
List<Transaction> transactionForCurrency = transactionByCurrencies.get(currency);
if (transactionForCurrency == null) {
transactionForCurrency = new ArrayList<>();
transactionByCurrencies.put(currency, transactionForCurrency);
}
transactionForCurrency.add(transaction);
}
ํตํ๋ณ๋ก ํธ๋์ญ์ ๋ฆฌ์คํธ๋ฅผ ๊ทธ๋ฃนํํ๊ธฐ ์ํด ์์ ๊ฐ์ ๋ฐฉ๋ฒ๋ ์์ง๋ง ์๋ฐ8์์๋ ๋ ๊ฐ๊ฒฐํ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
Map<Currency, List<Transaction>> transactionByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
์ปฌ๋ ํฐ๋ ๋ฌด์์ธ๊ฐ?
Collector ์ธํฐํ์ด์ค ๊ตฌํ์ ์คํธ๋ฆผ์ ์์๋ฅผ ์ด๋ค ์์ผ๋ก ๋์ถํ ์ง ์ง์ ํ๋ค. 5์ฅ์์๋ '๊ฐ ์์๋ฅผ ๋ฆฌ์คํธ๋ก ๋ง๋ค์ด๋ผ'๋ฅผ ์๋ฏธํ๋ toList
๋ฅผ Collector
์ธํฐํ์ด์ค์ ๊ตฌํ์ผ๋ก ์ฌ์ฉํ๋ค. ์ฌ๊ธฐ์๋ groupingBy
๋ฅผ ์ด์ฉํด์ '๊ฐ ํค(ํตํ) ๋ฒํท ๊ทธ๋ฆฌ๊ณ ๊ฐ ํค ๋ฒํท์ ๋์ํ๋ ์์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ผ๋ก ํฌํจํ๋ ๋งต์ ๋ง๋ค๋ผ'๋ ๋์์ ์ํํ๋ค.
collect()
๋ฉ์๋๋ก Collector
์ธํฐํ์ด์ค ๊ตฌํ์ ์ ๋ฌํ๋ค. ์คํธ๋ฆผ์ collect
๋ฅผ ํธ์ถํ๋ฉด ์คํธ๋ฆผ์ ์์์ ๋ด๋ถ์ ์ผ๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์ด ์ํ๋๋ค. ํตํ ์์ ์์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ฒ๋ผ Collector
์ธํฐํ์ด์ค์ ๋ฉ์๋๋ฅผ ์ด๋ป๊ฒ ๊ตฌํํ๋๋์ ๋ฐ๋ผ ์คํธ๋ฆผ์ ์ด๋ค ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ํํ ์ง ๊ฒฐ์ ๋๋ค. Collectors
์ ํธ๋ฆฌํฐ ํด๋์ค๋ ์์ฃผ ์ฌ์ฉํ๋ ์ปฌ๋ ํฐ ์ธ์คํด์ค๋ฅผ ์์ฝ๊ฒ ์์ฑํ ์ ์๋ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค. ex) toList()
, counting()
๋ฆฌ๋์ฑ๊ณผ ์์ฝ
์ฒซ ๋ฒ์งธ ์์ ๋ก counting()
์ด๋ผ๋ ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ๋ฐํํ๋ ์ปฌ๋ ํฐ๋ก ๋ฉ๋ด์์ ์๋ฆฌ ์๋ฅผ ๊ณ์ฐํ๋ค.
long howMayDishes = menu.stream().collect(counting());
๋ ๋ฒ์งธ๋ ๋ฉ๋ด์์ ์นผ๋ก๋ฆฌ๊ฐ ๊ฐ์ฅ ๋์ ์๋ฆฌ๋ฅผ ์ฐพ๋๋ค๊ณ ํด๋ณด์. Collectors.maxBy
, Collectors.minBy
๋ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์คํธ๋ฆผ์ ์ต๋๊ฐ๊ณผ ์ต์๊ฐ์ ๊ณ์ฐํ ์ ์๋ค. ๋ ์ปฌ๋ ํฐ๋ ์คํธ๋ฆผ์ ์์๋ฅผ ๋น๊ตํ๋๋ฐ ์ฌ์ฉํ Comparator
๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค.
Comparator<Dish> dishCaloriesComparator
= Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish
= menu.stream().collect(maxBy(dishCaloriesComparator));
๋ํ ์คํธ๋ฆผ์ ์๋ ๊ฐ์ฒด์ ์ซ์ ํ๋์ ํฉ๊ณ๋ ํ๊ท ๋ฑ์ ๋ฐํํ๋ ์ฐ์ฐ์๋ ๋ฆฌ๋์ฑ ๊ธฐ๋ฅ์ด ์์ฃผ ์ฌ์ฉ๋๋ค. ์ด๋ฌํ ์ฐ์ฐ์ ์์ฝ ์ฐ์ฐ์ด๋ผ ๋ถ๋ฅธ๋ค.
๋ค์์ ๋ฉ๋ด ๋ฆฌ์คํธ์ ์ด ์นผ๋ก๋ฆฌ๋ฅผ ๊ณ์ฐํ๋ ์ฝ๋๋ค.
int totalCalories
= menu.stream.collect(summingInt(Dish::getCalories));
summingInt
๋ฟ๋ง ์๋๋ผ summingLong
, summingDouble
, averagingInt
, averagingLong
, averagingDouble
๋ฑ ๋ค์ํ ํ์์ด ์กด์ฌํ๋ค.
double avgCalories
= menu.stream.collect(averagingInt(Dish::getCalories));
๋ ๊ฐ ์ด์์ ์ฐ์ฐ์ ํ ๋ฒ์ ์ํํด์ผ ํ ๋๋ ์๋ค. ์ด๋ฐ ์ํฉ์์๋ ํฉํ ๋ฆฌ ๋ฉ์๋ summarizingInt
๊ฐ ๋ฐํํ๋ ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ์๋ฅผ ๋ค์ด ๋ค์์ ํ๋์ ์์ฝ ์ฐ์ฐ์ผ๋ก ๋ฉ๋ด์ ์๋ ์์์, ํฉ๊ณ, ํ๊ท , min, max ๋ฑ์ ๊ณ์ฐํ๋ ์ฝ๋๋ค.
IntSummaryStatistics menuStatistics
= menu.stream().collect(summarizingInt(Dish::getCalories));
// IntSummaryStatistics { count=9, sum=4300, min=120, average=477.777778, max=800 }
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด IntSummaryStatistics
ํด๋์ค๋ก ๋ชจ๋ ์ ๋ณด๊ฐ ์์ง๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก int
๋ฟ ์๋๋ผ long
์ด๋ double
์ ๋์ํ๋ summarizingLong
, summarizingDouble
๋ฉ์๋์ ๊ด๋ จ๋ LongSummaryStatistics
, DoubleSummaryStatistics
ํด๋์ค๋ ์๋ค.
๋ฌธ์์ด ์ฐ๊ฒฐ
๋ฌธ์์ด ์ฐ๊ฒฐ์ ์ํด joining
๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก StringBuilder
๋ฅผ ์ด์ฉํด์ ๋ฌธ์์ด์ ํ๋๋ก ๋ง๋ ๋ค. ์ถ๊ฐ์ ์ผ๋ก ์ฐ๊ฒฐ๋ ๋ฌธ์์ด๋ค ์ฌ์ด์ ๊ตฌ๋ถ ๋ฌธ์์ด์ ๋ฃ์ ์ ์๋๋ก ์ค๋ฒ๋ก๋๋ joining
ํฉํ ๋ฆฌ ๋ฉ์๋๋ ์๋ค.
String shortMenu
= menu.stream().map(Dish::getName).collect(joining(","));
// ์ฝ๋ ์คํ ๊ฒฐ๊ณผ
// pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
๋ฒ์ฉ ๋ฆฌ๋์ฑ ์์ฝ ์ฐ์ฐ
์ง๊ธ๊น์ง ์ดํด๋ณธ ๋ชจ๋ ์ปฌ๋ ํฐ๋ reducing
ํฉํ ๋ฆฌ ๋ฉ์๋๋ก๋ ์ ์ํ ์ ์๋ค. ์ฆ ๋ฒ์ฉ Collectors.reducing
์ผ๋ก๋ ๊ตฌํํ ์ ์๋ค.
int totalCalories
= menu.stream().collect(reducing(0, Dish::getCalories, (i,j) ->
i+j
));
reducing
์ ์ธ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ๋๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๋ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์์๊ฐ์ด๊ฑฐ๋ ์คํธ๋ฆผ์ ์ธ์๊ฐ ์์ ๋๋ ๋ฐํ๊ฐ์ด๋ค. ๋ ๋ฒ์งธ ์ธ์๋ ๋ณํ ํจ์๋ค. ์ธ ๋ฒ์งธ ์ธ์๋ ๊ฐ์ ์ข
๋ฅ์ ๋ ํญ๋ชฉ์ ํ๋์ ๊ฐ์ผ๋ก ๋ํ๋ BinaryOperator
๋ค.
๋ค์์ฒ๋ผ ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ์ง reducing
๋ฒ์ ์ ์ด์ฉํด์ ๊ฐ์ฅ ์นผ๋ก๋ฆฌ๊ฐ ๋์ ์๋ฆฌ๋ฅผ ์ฐพ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
Optional<Dish> mostCalorieDish
= menu.stream().collect(reducing((d1, d2) ->
d1.getCaloriees() > d2.getCalories() ? d1 : d2
));
์ปฌ๋ ์ ํ๋ ์์ํฌ ์ ์ฐ์ฑ: ๊ฐ์ ์ฐ์ฐ๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ํํ ์ ์๋ค.
์ด์ ์์ ์ ๋๋คํํ์ ๋์ Integer
ํด๋์ค์ sum
๋ฉ์๋ ๋ ํผ๋ฐ์ค๋ฅผ ์ด์ฉํ๋ฉด ์ฝ๋๋ฅผ ์ข ๋ ๋จ์ํํ ์ ์๋ค.
int totalCalories
= menu.stream().collect(reducing(
0, // ์ด๊ธฐ๊ฐ
Dish::getCalories, //๋ณํ ํจ์
Integer::sum
)); // ํฉ๊ณ ํจ์
๋ ์ปฌ๋ ํฐ๋ฅผ ์ด์ฉํ์ง ์๋ ๋ฐฉ๋ฒ๋ ์๋ค.
int totalCalories
= menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
reduce(Integer::sum)
๋ ๋น ์คํธ๋ฆผ๊ณผ ๊ด๋ จํ ๋ ๋ฌธ์ ๋ฅผ ํผํ ์ ์๋๋ก int
๊ฐ ์๋ Optional<Integer>
๋ฅผ ๋ฐํํ๋ค. ๊ทธ๋ฆฌ๊ณ get
์ผ๋ก Optional
๊ฐ์ฒด ๋ด๋ถ์ ๊ฐ์ ์ถ์ถํ๋ค. ์๋ฆฌ ์คํธ๋ฆผ์ ๋น์ด์์ง ์๋ค๋ ์ฌ์ค์ ์๊ณ ์์ผ๋ฏ๋ก get์ ์์ ๋กญ๊ฒ ์ฌ์ฉํ ์ ์๋ค. ๋ง์ง๋ง์ผ๋ก ์คํธ๋ฆผ์ IntStream
์ผ๋ก ๋งคํํ ๋ค์์ sum
๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
๊ทธ๋ฃนํ
๋ฉ๋ด๋ฅผ ๊ทธ๋ฃนํํ๋ค๊ณ ๊ฐ์ ํ์. ์๋ฅผ ๋ค์ด ๊ณ ๊ธฐ๋ฅผ ํฌํจํ๋ ๊ทธ๋ฃน, ์์ ์ ํฌํจํ๋ ๊ทธ๋ฃน, ๋๋จธ์ง ๊ทธ๋ฃน์ผ๋ก ๋ฉ๋ด๋ฅผ ๊ทธ๋ฃนํํ ์ ์๋ค. ๋ค์์ฒ๋ผ ํฉํ ๋ฆฌ ๋ฉ์๋ Collectors.groupingBy
๋ฅผ ์ด์ฉํด์ ์ฝ๊ฒ ๋ฉ๋ด๋ฅผ ๊ทธ๋ฃนํํ ์ ์๋ค.
Map<Dish.Type, List<Dish>> dishesByType
= menu.stream().collect(groupingBy(Dish::getType));
// ๋ค์์ `Map`์ ํฌํจ๋ ๊ฒฐ๊ณผ๋ค.
// {Fish=[prawns, salmon], OTHER=[french fries, rice], MEAT=[pork, beef, chicken]}
์คํธ๋ฆผ์ ๊ฐ ์๋ฆฌ์์ Dish.Type
๊ณผ ์ผ์นํ๋ ๋ชจ๋ ์๋ฆฌ๋ฅผ ์ถ์ถํ๋ ํจ์๋ฅผ groupingBy
๋ฉ์๋๋ก ์ ๋ฌํ๋ค. ์ด ํจ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์คํธ๋ฆผ์ด ๊ทธ๋ฃนํ๋๋ฏ๋ก ์ด๋ฅผ ๋ถ๋ฅ ํจ์๋ผ๊ณ ๋ถ๋ฅธ๋ค.
๊ทธ๋ฐ๋ฐ ์์ ๊ฐ์ด ๋จ์ํ ๋ถ๋ฅ ๊ธฐ์ค์ด ์๋ ๋ณต์กํ ๋ถ๋ฅ ๊ธฐ์ค์ด ํ์ํ ์ํฉ์์๋ ๋ฉ์๋ ๋ ํผ๋ฐ์ค๋ฅผ ๋ถ๋ฅ ํจ์๋ก ์ฌ์ฉํ ์ ์๋ค. ์๋ฅผ ๋ค์ด 400์นผ๋ก๋ฆฌ ์ดํ๋ฅผ 'diet
'๋ก, 400~700์นผ๋ก๋ฆฌ๋ฅผ 'normal
'๋ก, 700์นผ๋ก๋ฆฌ ์ด๊ณผ๋ฅผ 'fat
' ์๋ฆฌ๋ก ๋ถ๋ฅํ๋ค๊ณ ๊ฐ์ ํ์. Dish ํด๋์ค์๋ ์ด๋ฌํ ์ฐ์ฐ์ ํ์ํ ๋ฉ์๋๊ฐ ์์ผ๋ฏ๋ก ๋ฉ์๋ ๋ ํผ๋ฐ์ค๋ฅผ ๋ถ๋ฅ ํจ์๋ก ์ฌ์ฉํ ์ ์๋ค. ๋ฐ๋ผ์ ๋ค์ ์์ ์ฒ๋ผ ๋๋ค ํํ์์ผ๋ก ํ์ํ ๋ก์ง์ ๊ตฌํํด์ผ ํ๋ค.
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;
})
);
๋ค์์ค ๊ทธ๋ฃนํ
์ง๊ธ๊น์ง ๋ฉ๋ด์ ์๋ฆฌ๋ฅผ ์ข ๋ฅ ๋๋ ์นผ๋ก๋ฆฌ๋ก ๊ทธ๋ฃนํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ดค๋ค. ๊ทธ๋ฌ๋ฉด ์๋ฆฌ ์ข ๋ฅ์ ์นผ๋ก๋ฆฌ ๋ ๊ฐ์ง ๊ธฐ์ค์ผ๋ก ๋์์ ๊ทธ๋ฃนํํ ์ ์์๊น?
๋ ์ธ์๋ฅผ ๋ฐ๋ ํฉํ ๋ฆฌ ๋ฉ์๋ Collections.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;
})));
๋ถํ
๋ถํ ์ ๋ถํ ํจ์๋ผ ๋ถ๋ฆฌ๋ ํ๋ ๋์ผ์ดํธ๋ฅผ ๋ถ๋ฅ ํจ์๋ก ์ฌ์ฉํ๋ ํน์ํ ๊ทธ๋ฃนํ ๊ธฐ๋ฅ์ด๋ค. ๋ถํ ํจ์๋ ๋ถ๋ฆฐ์ ๋ฐํํ๋ฏ๋ก ๋งต์ ํค ํ์์ Boolean
์ด๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ทธ๋ฃนํ ๋งต์ ์ต๋ ๋ ๊ฐ(T/F)์ ๊ทธ๋ฃน์ผ๋ก ๋ถ๋ฅ๋๋ค. ์๋ฅผ ๋ค์ด ์ฑ์์ฃผ์์ ์น๊ตฌ๋ฅผ ์ ๋
์ ์ด๋ํ๋ค๊ณ ๊ฐ์ ํ์. ๊ทธ๋ฌ๋ฉด ์ด์ ๋ชจ๋ ์๋ฆฌ๋ฅผ ์ฑ์ ์๋ฆฌ์ ์ฑ์์ด ์๋ ์๋ฆฌ๋ก ๋ถ๋ฅ ํด์ผ ํ๋ค.
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian));
// ์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋งต์ด ๋ฐํ๋๋ค.
// {false=[pork, beef, chicken, prawns, salmon],
// true=[french fries, rice, season fruit, pizza]}
์ด์ ์ฐธ๊ฐ์ ํค๋ก ๋งต์์ ๋ชจ๋ ์ฑ์ ์๋ฆฌ๋ฅผ ์ป์ ์ ์๋ค.
List<Dish> vegetarianDishes = partitionedMenu.get(true);
๋ฌผ๋ก ์ด์ ์์ ์์ ์ฌ์ฉํ ํ๋ ๋์ผ์ดํธ๋ก ํํฐ๋งํ ๋ค์์ ๋ณ๋์ ๋ฆฌ์คํธ์ ๊ฒฐ๊ณผ๋ฅผ ์์งํด๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
List<Dish> vegetarianDishes = menu.stream().filter(Dish::isVegetarian).collect(toList());