๐น chap6. ์คํธ๋ฆผ ์ผ๋ก ๋ฐ์ดํฐ ์์ง
- ํตํ๋ณ๋ก ํธ๋์ญ์ ์ ๊ทธ๋ฃนํํ ์ฝ๋(๋ช ๋ นํ ๋ฒ์ )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ๊ทธ๋ฃนํํ ํธ๋์ญ์
์ ์ ์ฅํ ๋งต ์์ฑ
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction: transactions) {
Currency currency = transaction.getCurrency(); // ํธ๋์ญ์
์ ํตํ๋ฅผ ์ถ์ถ
List<Transaction> transactionsForCurrency =
transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
// ํ์ฌ ํตํ๋ฅผ ๊ทธ๋ฃนํํ๋ ๋งต์ ํญ๋ชฉ์ด ์์ผ๋ฉด ํญ๋ชฉ์ ๋ง๋ ๋ค.
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
// ๊ฐ์ ํตํ๋ฅผ ๊ฐ์ง ํธ๋์ญ์
๋ฆฌ์คํธ์ ํ์ฌ ํ์ ์ค์ธ ํธ๋์ญ์
์ ์ถ๊ฐํ๋ค.
transactionsForCurrency.add(transaction);
}
โํตํ๋ณ๋ก ํธ๋์ญ์ ๋ฆฌ์คํธ๋ฅผ ๊ทธ๋ฃนํํ์์คโ๋ผ๊ณ ๊ฐ๋จํ ํํํ ์ ์์ง๋ง ์ฝ๋๊ฐ ๋ฌด์์ ์คํํ๋์ง ํ ๋์ ํ์ ํ๊ธฐ ์ด๋ ต๋ค. Stream์ toList๋ฅผ ์ฌ์ฉํ๋ ๋์ ๋ ๋ฒ์ฉ์ ์ธ ์ปฌ๋ ํฐ ํ๋ผ๋ฏธํฐ๋ฅผ collect ๋ฉ์๋์ ์ ๋ฌํจ์ผ๋ก์จ ์ํ๋ ์ฐ์ฐ์ ๊ฐ๊ฒฐํ๊ฒ ๊ตฌํํ ์ ์๋ค.
- ํตํ๋ณ๋ก ํธ๋์ญ์ ์ ๊ทธ๋ฃนํํ ์ฝ๋(ํจ์ํ ๋ฒ์ )
1
2
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
6.1 ์ปฌ๋ ํฐ๋ ๋ฌด์์ธ๊ฐ?
์ ์์ ๋ ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ์ ๋นํด ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ด ์ผ๋ง๋ ํธ๋ฆฌํ์ง ๋ช ํํ๊ฒ ๋ณด์ฌ์ค๋ค.
ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์๋ โ๋ฌด์โ์ ์ํ๋์ง ์ง์ ๋ช ์ํ ์ ์์ด์ ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก ์ด๋ฅผ ์ป์์ง๋ ์ ๊ฒฝ ์ธ ํ์๊ฐ ์๋ค. ์ฌ๊ธฐ์๋ groupingBy๋ฅผ ์ด์ฉํด์ โ๊ฐ ํค(ํตํ) ๋ฒํท ๊ทธ๋ฆฌ๊ณ ๊ฐ ํค ๋ฒํท์ ๋์ํ๋ ์์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ผ๋ก ํฌํจํ๋ ๋งต(Map)์ ๋ง๋ค๋ผโ๋ ๋์์ ์ํํ๋ค.
๋ช ๋ นํ ์ฝ๋์์๋ ๋ค์ค ๋ฃจํ์ ์กฐ๊ฑด๋ฌธ์ ์ถ๊ฐํ๋ฉฐ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ํฌ๊ฒ ๋จ์ด์ง๋ค.
6.1.1 ๊ณ ๊ธ ๋ฆฌ๋์ฑ ๊ธฐ๋ฅ์ ํ๋ ์ปฌ๋ ํฐ
ํจ์ํ API์ ์ฅ์ โ ๋์ ์์ค์ ์กฐํฉ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ
์ปฌ๋ ํฐ์ ์ต๋ ๊ฐ์ ์ collect๋ก ๊ฒฐ๊ณผ๋ฅผ ์์งํ๋ ๊ณผ์ ์ ๊ฐ๋จํ๋ฉด์๋ ์ ์ฐํ ๋ฐฉ์์ผ๋ก ์ ์ํ ์ ์๋ค๋ ์ ์ด๋ค.
- ์คํธ๋ฆผ์ collect๋ฅผ ํธ์ถํ๋ฉด ์คํธ๋ฆผ์ ์์์ ๋ด๋ถ์ ์ผ๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์ด ์ํ๋จ
- ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ์์๋ ์ฐ๋ฆฌ๊ฐ ์ง์ ๊ตฌํํด์ผ ํ๋ ์์ ์ด ์๋์ผ๋ก ์ํ๋จ
- collect์์๋ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ด์ฉํด์ ์คํธ๋ฆผ์ ๊ฐ ์์๋ฅผ ๋ฐฉ๋ฌธํ๋ฉด์ ์ปฌ๋ ํฐ๊ฐ ์์ ์ ์ฒ๋ฆฌํ๋ค.
Collectors ์ ํธ๋ฆฌํฐ ํด๋์ค๋ ์์ฃผ ์ฌ์ฉํ๋ ์ปฌ๋ ํฐ ์ธ์คํด์ค๋ฅผ ์์ฝ๊ฒ ์์ฑํ ์ ์๋ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค. ์๋ฅผ ๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ์ง๊ด์ ์ธ ๋ฉ์๋๋ก toList๋ฅผ ๊ผฝ์ ์ ์๋ค. toList๋ ์คํธ๋ฆผ์ ๋ชจ๋ ์์๋ฅผ ๋ฆฌ์คํธ๋ก ์์งํ๋ค.
1
2
List<Transaction> transactions =
transactionStream.collect(Collectors.toList());
6.1.2 ๋ฏธ๋ฆฌ ์ ์๋ ์ปฌ๋ ํฐ
groupingBy ๊ฐ์ด Collectors ํด๋์ค์์ ์ ๊ณตํ๋ ํฉํ ๋ฆฌ ๋ฉ์๋์ ๊ธฐ๋ฅ์ ์ค๋ช ํ๋ค.
- Collectors์์ ์ ๊ณตํ๋ ๋ฉ์๋์ ๊ธฐ๋ฅ
์คํธ๋ฆผ ์์๋ฅผ ํ๋์ ๊ฐ์ผ๋ก ๋ฆฌ๋์คํ๊ณ ์์ฝ
: ์ด์ ์์ ์ฒ๋ผ ํธ๋์ญ์ ๋ฆฌ์คํธ์์ ํธ๋์ญ์ ์ดํฉ์ ์ฐพ๋ ๋ฑ์ ๋ค์ํ ๊ณ์ฐ์ ์ํํ ๋ ์ ์ฉ
์์ ๊ทธ๋ฃนํ
: ์ด์ ์์ ๋ฅผ ๋ค์์ค์ผ๋ก ๊ทธ๋ฃนํํ๊ฑฐ๋ ๊ฐ๊ฐ์ ๊ฒฐ๊ณผ ์๋ธ๊ทธ๋ฃน์ ์ถ๊ฐ๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ ์ฉํ ์ ์์ (๋ค์ํ ์ปฌ๋ ํฐ ์กฐํฉ์ ํตํด)
์์ ๋ถํ
: ํ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ์ boolean์ ๋ฐํํ๋ ํจ์, ์ฆ ํ๋ ๋์ผ์ดํธ๋ฅผ ๊ทธ๋ฃนํ ํจ์๋ก ์ฌ์ฉ
6.2 ๋ฆฌ๋์ฑ๊ณผ ์์ฝ
counting()
counting() ์ด๋ผ๋ ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ๋ฐํํ๋ ์ปฌ๋ ํฐ๋ก ๋ฉ๋ด์์ ์๋ฆฌ ์๋ฅผ ๊ณ์ฐ
1
long howManyDishes = menu.stream().collect(Collectors.counting());
๋ค์์ฒ๋ผ ๋ถํ์ํ ๊ณผ์ ์ ์๋ตํ ์ ์๋ค.
1
long howManyDishes = menu.stream().count();
counting ์ปฌ๋ ํฐ๋ ๋ค๋ฅธ ์ปฌ๋ ํฐ์ ํจ๊ป ์ฌ์ฉํ ๋ ์๋ ฅ์ ๋ฐํํ๋ค.
Collectors ํด๋์ค์ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ๋ชจ๋ ์ํฌํธ
1
import static java.util.stream.Collectors.*;
Collectors.counting()์ ๊ฐ๋จํ๊ฒ counting()์ผ๋ก ํํํ ์ ์๋ค.
6.2.1 ์คํธ๋ฆผ๊ฐ์์ ์ต๋๊ฐ๊ณผ ์ต์๊ฐ ๊ฒ์
๋ฉ๋ด์์ ๊ฐ์ฅ ์นผ๋ก๋ฆฌ๊ฐ ๋์ ์๋ฆฌ๋ฅผ ์ฐพ๋๋ค๊ณ ๊ฐ์ ํ์.
Collectors.maxBy, Collectors.minBy ๋ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์คํธ๋ฆผ์ ์ต๋๊ฐ๊ณผ ์ต์๊ฐ์ ๊ณ์ฐํ ์ ์๋ค. ๋ ์ปฌ๋ ํฐ๋ ์คํธ๋ฆผ์ ์์๋ฅผ ๋น๊ตํ๋ ๋ฐ ์ฌ์ฉํ Comparator๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค.
1
2
3
4
5
6
Comparator<Dish> dishCaloriesComparator =
Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish =
menu.stream()
.collect(maxBy(dishCaloriesComparator));
Opitonal
๋ํ ์คํธ๋ฆผ์ ์๋ ๊ฐ์ฒด์ ์ซ์ ํ๋์ ํฉ๊ณ๋ ํ๊ท ๋ฑ์ ๋ฐํํ๋ ์ฐ์ฐ์๋ ๋ฆฌ๋์ฑ ๊ธฐ๋ฅ์ด ์์ฃผ ์ฌ์ฉ๋๋ค. ์ด๋ฌํ ์ฐ์ฐ์ ์์ฝ(summarization) ์ฐ์ฐ์ด๋ผ ๋ถ๋ฅธ๋ค.
6.2.2 ์์ฝ ์ฐ์ฐ
summingInt(), summarizingInt()
Collectors ํด๋์ค๋ Collectors.summingInt๋ผ๋ ํน๋ณํ ์์ฝ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
- ๊ฐ์ฒด๋ฅผ int๋ก ๋งคํํ๋ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค.
- summingInt์ ์ธ์๋ก ์ ๋ฌ๋ ํจ์๋ ๊ฐ์ฒด๋ฅผ int๋ก ๋งคํํ ์ปฌ๋ ํฐ๋ฅผ ๋ฐํํ๋ค.
- summingInt๊ฐ collect ๋ฉ์๋๋ก ์ ๋ฌ๋๋ฉด ์์ฝ ์์ ์ ์ํํ๋ค.
1
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
์นผ๋ก๋ฆฌ๋ก ๋งคํ๋ ๊ฐ ์๋ฆฌ์ ๊ฐ์ ํ์ํ๋ฉด์ ์ด๊น๊ฐ(์ฌ๊ธฐ์๋ 0)์ผ๋ก ์ค์ ๋์ด ์๋ ๋์ ์์ ์นผ๋ก๋ฆฌ๋ฅผ ๋ํ๋ค.
Collectors.summingLong๊ณผ Collectors.summingDouble ๋ฉ์๋๋ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ ๊ฐ๊ฐ long ๋๋ double ํ์์ ๋ฐ์ดํฐ๋ก ์์ฝํ๋ค๋ ์ ๋ง ๋ค๋ฅด๋ค.
์ด์ธ์ ํ๊ท ๊ฐ ๊ณ์ฐ ๋ฑ์ ์ฐ์ฐ๋ ์์ฝ ๊ธฐ๋ฅ์ผ๋ก ์ ๊ณต๋๋ค. ์ฆ, Collectors.averagingInt, averagingLong, averagingDouble ๋ฑ์ผ๋ก ๋ค์ํ ํ์์ผ๋ก ์ด๋ฃจ์ด์ง ์ซ์ ์งํฉ์ ํ๊ท ์ ๊ณ์ฐํ ์ ์๋ค.
1
2
double avgCalories =
menu.stream.collect(averagingDouble(Dish::getCalories));
์ปฌ๋ ํฐ๋ก ์ข ์ข ๋ ๊ฐ ์ด์์ ์ฐ์ฐ์ ํ ๋ฒ์ ์ํํด์ผ ํ ๋๋ ์๋ค. ์ด๋ฐ ์ํฉ์์๋ ํฉํ ๋ฆฌ ๋ฉ์๋ summarizingInt๊ฐ ๋ฐํํ๋ ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ์๋ฅผ ๋ค์ด ๋ค์์ ํ๋์ ์์ฝ ์ฐ์ฐ์ผ๋ก ๋ฉ๋ด์ ์๋ ์๋ฆฌ ์, ์๋ฆฌ์ ์นผ๋ก๋ฆฌ ํฉ๊ณ, ํ๊ท , ์ต๋๊ฐ, ์ต์๊ฐ ๋ฑ์ ๊ณ์ฐํ๋ ์ฝ๋๋ค.
1
2
IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด IntSummaryStatistics ํด๋์ค๋ก ๋ชจ๋ ์ ๋ณด๊ฐ ์์ง๋๋ค. menuStatistics ๊ฐ์ฒด๋ฅผ ์ถ๋ ฅํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ค.
1
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
๋ง์ฐฌ๊ฐ์ง๋ก int๋ฟ๋ง ์๋๋ผ long์ด๋ double์ ๋์ํ๋ summarizingLong, summarizingDouble ๋ฉ์๋์ ๊ด๋ จ๋ LongSummaryStatistics, DoubleSummaryStatistics ํด๋์ค๋ ์๋ค.
6.2.3 ๋ฌธ์์ด ์ฐ๊ฒฐ
joinning()
์ปฌ๋ ํฐ์ joining ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ์คํธ๋ฆผ์ ๊ฐ ๊ฐ์ฒด์ toString ๋ฉ์๋๋ฅผ ํธ์ถํด์ ์ถ์ถํ ๋ฌธ์์ด์ ํ๋์ ๋ฌธ์์ด๋ก ์ฐ๊ฒฐํด์ ๋ฐํํ๋ค.
1
String shortMenu = menu.stream.map(Dish::getName).collect(joinning());
joinning ๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก StringBuilder๋ฅผ ์ด์ฉํด์ ๋ฌธ์์ด์ ํ๋๋ก ๋ง๋ ๋ค. Dish ํด๋์ค๊ฐ ์๋ฆฌ๋ช ์ ๋ฐํํ๋ toString ๋ฉ์๋๋ฅผ ํฌํจํ๊ณ ์๋ค๋ฉด ๋ค์ ์ฝ๋์ฒ๋ผ map์ผ๋ก ๊ฐ ์๋ฆฌ์ ์ด๋ฆ์ ์ถ์ถํ๋ ๊ณผ์ ์ ์๋ตํ ์ ์๋ค.
1
String shortMenu = menu.stream().collect(joinning());
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
1
porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon
ํ์ง๋ง ๊ฒฐ๊ณผ ๋ฌธ์์ด์ ํด์ํ ์๊ฐ ์๋ค. ๋ ์์ ์ฌ์ด์ ๊ตฌ๋ถ ๋ฌธ์์ด์ ๋ฃ์ ์ ์๋๋ก ์ค๋ฒ๋ก๋๋ joinning ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
1
String shortMenu = menu.stream().map(Dish::getName).collect(joinning(", "));
๋ค์์ ์ ์ฝ๋๋ฅผ ์คํํ ๊ฒฐ๊ณผ์ด๋ค.
1
pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
์ง๊ธ๊น์ง ์คํธ๋ฆผ์ ํ๋์ ๊ฐ์ผ๋ก ๋ฆฌ๋์ค ํ๋ ๋ค์ํ ์ปฌ๋ ํฐ๋ฅผ ํ์ธํด๋ณด์๋ค.
6.2.4 ๋ฒ์ฉ ๋ฆฌ๋์ฑ ์์ฝ ์ฐ์ฐ
์ง๊ธ๊น์ง ์ดํด๋ณธ ๋ชจ๋ ์ปฌ๋ ํฐ๋ reducing ํฉํ ๋ฆฌ ๋ฉ์๋๋ก๋ ์ ์ํ ์ ์๋ค. ์ฆ, ๋ฒ์ฉ Collectors.reducing์ผ๋ก๋ ๊ตฌํํ ์ ์๋ค. ์๋ฅผ ๋ค์ด ๋ค์ ์ฝ๋์ฒ๋ผ reducing ๋ฉ์๋๋ก ๋ง๋ค์ด์ง ์ปฌ๋ ํฐ๋ก๋ ๋ฉ๋ด์ ๋ชจ๋ ์นผ๋ก๋ฆฌ ํฉ๊ณ๋ฅผ ๊ณ์ฐํ ์ ์๋ค.
1
2
int totalCalories =
menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
reducing์ ์ธ์ ์ธ ๊ฐ๋ฅผ ๋ฐ๋๋ค.
- ์ฒซ ๋ฒ์งธ ์ธ์ : ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์์๊ฐ์ด๊ฑฐ๋ ์คํธ๋ฆผ์ ์ธ์๊ฐ ์์ ๋๋ ๋ฐํ๊ฐ (์ซ์ ํฉ๊ณ์์๋ ์ธ์๊ฐ ์์ ๋ ๋ฐํ๊ฐ์ผ๋ก 0์ด ์ ํฉํ๋ค.)
- ๋ ๋ฒ์งธ ์ธ์ : ์๋ฆฌ๋ฅผ ์นผ๋ก๋ฆฌ ์ ์๋ก ๋ณํํ ๋ ์ฌ์ฉํ ๋ณํ ํจ์
- ์ธ ๋ฒ์งธ ์ธ์ : ๊ฐ์ ์ข ๋ฅ์ ๋ ํญ๋ชฉ์ ํ๋์ ๊ฐ์ผ๋ก ๋ํ๋ BinaryOperator (์์ ์์๋ ๋ ๊ฐ์ int๊ฐ ์ฌ์ฉ๋์๋ค.)
๋ค์์ฒ๋ผ ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ์ง reducing ๋ฒ์ ์ ์ด์ฉํด์ ๊ฐ์ฅ ์นผ๋ก๋ฆฌ๊ฐ ๋์ ์๋ฆฌ๋ฅผ ์ฐพ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
1
2
3
Optional<Dish> mostCalorieDish =
menu.stream().collect(reducing(
(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing ํฉํ ๋ฆฌ ๋ฉ์๋๋ ์ธ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing ๋ฉ์๋์์ ์คํธ๋ฆผ์ ์ฒซ ๋ฒ์งธ ์์๋ฅผ ์์ ์์, ์ฆ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ฐ์ผ๋ฉฐ, ์์ ์ ๊ทธ๋๋ก ๋ฐํํ๋ ํญ๋ฑ ํจ์๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ๋ฐ๋ ์ํฉ์ ํด๋นํ๋ค. ์ฆ, ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing ์ปฌ๋ ํฐ๋ ์์๊ฐ์ด ์์ผ๋ฏ๋ก ๋น ์คํธ๋ฆผ์ด ๋๊ฒจ์ก์ ๋ ์์๊ฐ์ด ์ค์ ๋์ง ์๋ ์ํฉ์ด ๋ฒ์ด์ง๋ค. ๊ทธ๋์ **ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing์ Optional
collect์ reduce
Stream ์ธํฐํ์ด์ค์ collect์ reduce์ ์ฐจ์ด์ ์ ๋ฌด์์ผ๊น? ๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์๋ค.
๋ค์ ์ฝ๋์์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ฒ๋ผ toList ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํ๋ collect ๋์ reduce ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
1
2
3
4
5
6
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> numbers
= 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;});
์ ์ฝ๋์๋ ์๋ฏธ๋ก ์ ์ธ ๋ฌธ์ ์ ์ค์ฉ์ฑ ๋ฌธ์ ๋ฑ ๋ ๊ฐ์ง ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์๋ฏธ๋ก ์ ๋ฌธ์
collect ๋ฉ์๋๋ ๋์ถํ๋ ค๋ ๊ฒฐ๊ณผ๋ฅผ ๋์ ํ๋ ์ปจํ ์ด๋๋ฅผ ๋ฐ๊พธ๋๋ก ์ค๊ณ๋ ๋ฉ์๋์ธ ๋ฐ๋ฉด reduce๋ ๋ ๊ฐ์ ํ๋๋ก ๋์ถํ๋ ๋ถ๋ณํ ์ฐ์ฐ์ด๋ผ๋ ์
์์ ์์ reduce ๋ฉ์๋๋ ๋์ ์๋ก ์ฌ์ฉ๋ ๋ฆฌ์คํธ๋ฅผ ๋ณํ์ํค๋ฏ๋ก reduce๋ฅผ ์๋ชป ํ์ฉํ ์์ ํด๋นํ๋ค.
์ค์ฉ์ฑ ๋ฌธ์
์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ฅผ ๊ณ ์น๋ฉด ๋ฆฌ์คํธ ์์ฒด๊ฐ ๋ง๊ฐ์ ธ๋ฒ๋ฆฌ๋ฏ๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ๋ณ๋ ฌ๋ก ์ํํ ์ ์๋ค๋ ์
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋งค๋ฒ ์๋ก์ด ๋ฆฌ์คํธ๋ฅผ ํ ๋นํด์ผ ํ๋ค. โ ๊ฐ์ฒด ํ ๋น ๋๋ฌธ์ ์ฑ๋ฅ ์ ํ
๊ฐ๋ณ ์ปจํ ์ด๋ ๊ด๋ จ ์์ ์ด๋ฉด์ ๋ณ๋ ฌ์ฑ์ ํ๋ณดํ๋ ค๋ฉด collect ๋ฉ์๋๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ๊ตฌํํ๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค
์ปฌ๋ ์ ํ๋ ์์ํฌ ์ ์ฐ์ฑ : ๊ฐ์ ์ฐ์ฐ๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ํํ ์ ์๋ค.
reducing ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํ ์ด์ ์์ ์์ ๋๋ค ํํ์ ๋์ Integer ํด๋์ค์ sum ๋ฉ์๋ ์ฐธ์กฐ๋ฅผ ์ด์ฉํ๋ฉด ์ฝ๋๋ฅผ ์ข ๋ ๋จ์ํ ํ ์ ์๋ค.
1
2
3
4
5
6
7
8
int totalCalories =
menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
// sum ๋ฉ์๋ ์ฐธ์กฐ
int totalCalories =
menu.stream().collect(reducing(0, // ์ด๊น๊ฐ
Dish::getCalories, // ๋ณํ ํจ์
Integer::sum)); // ํฉ๊ณ ํจ์
๋ฆฌ๋์ฑ ์ฐ์ฐ๊ณผ์ ์ ๋์ ์๋ฅผ ์ด๊น๊ฐ์ผ๋ก ์ด๊ธฐํํ๊ณ , ํฉ๊ณ ํจ์๋ฅผ ์ด์ฉํด์ ๊ฐ ์์์ ๋ณํ ํจ์๋ฅผ ์ ์ฉํ ๊ฒฐ๊ณผ ์ซ์๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์กฐํฉํ๋ค.
counting ์ปฌ๋ ํฐ๋ ์ธ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ๊ตฌํํ ์ ์๋ค.
๋ค์ ์ฝ๋์ฒ๋ผ ์คํธ๋ฆผ์ Long ๊ฐ์ฒด ํ์์ ์์๋ฅผ 1๋ก ๋ณํํ ๋ค์์ ๋ชจ๋ ๋ํ ์ ์๋ค.
1
2
3
public static <T> Collector<T, ?, Long> counting() {
return reducing(0L, e -> 1L, Long::sum);
}
์ ๋ค๋ฆญ ์์ผ๋์นด๋ โ?โ ์ฌ์ฉ๋ฒ
์ ์์ ์์ counting ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ๋ฐํํ๋ ์ปฌ๋ ํฐ ์๊ทธ๋์ฒ์ ๋ ๋ฒ์งธ ์ ๋ค๋ฆญ ํ์์ผ๋ก ์์ผ๋์นด๋ ?์ด ์ฌ์ฉ๋์๋ค. ?๋ ์ปฌ๋ ํฐ์ ๋์ ์ ํ์์ด ์๋ ค์ง์ง ์์์(๋์ ์์ ํ์์ด ์์ ๋ก์)์ ์๋ฏธํ๋ค.
1
2
int totalCalories =
menu.stream().map(Dish::getCalories).reduce(Integer::sum).get
์ปฌ๋ ํฐ๋ฅผ ์ด์ฉํ์ง ์๊ณ ๊ฐ์ ์ฐ์ฐ์ ์ํํ ๊ฒ์ด๋ค.
ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reduce๋ฅผ ์คํธ๋ฆผ์ ์ ์ฉํ ๋ค๋ฅธ ์์ ์ ๋ง์ฐฌ๊ฐ์ง๋ก reduce(Integer::sum)๋ ๋น ์คํธ๋ฆผ๊ณผ ๊ด๋ จํ ๋ ๋ฌธ์ ๋ฅผ ํผํ ์ ์๋๋ก int๊ฐ ์๋ Optional<Integer>
๋ฅผ ๋ฐํํ๋ค. ๊ทธ๋ฆฌ๊ณ get์ผ๋ก Optional ๊ฐ์ฒด์ ๊ฐ์ ์ถ์ถํ๋ค. (์๋ฆฌ ์คํธ๋ฆผ์ด ๋น์ด์์ง ์๋ค๋ ์ฌ์ค์ ์๊ณ ์์ผ๋ฏ๋ก)
์ผ๋ฐ์ ์ผ๋ก๋ ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํ ์ ์๋ orElse, orElseGet ๋ฑ์ ์ด์ฉํด์ Optional์ ๊ฐ์ ์ป์ด์ค๋ ๊ฒ์ด ์ข๋ค. ๋ง์ง๋ง์ผ๋ก ์คํธ๋ฆ ์ IntStream์ผ๋ก ๋งคํํ ๋ค์์ sum ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
1
int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();
์์ ์ ์ํฉ์ ๋ง๋ ์ต์ ์ ํด๋ฒ ์ ํ
์ง๊ธ๊น์ง ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์๋ ํ๋์ ์ฐ์ฐ์ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ ์ ์์์ ๋ณด์ฌ์ค๋ค. ๋ํ ์คํธ๋ฆผ ์ธํฐํ์ด์ค์์ ์ง์ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ ๊ฒ์ ๋นํด ์ปฌ๋ ํฐ๋ฅผ ์ด์ฉํ๋ ์ฝ๋๊ฐ ๋ ๋ณต์กํ๋ค๋ ์ฌ์ค๋ ๋ณด์ฌ์ค๋ค. ์ฝ๋๊ฐ ์ข ๋ ๋ณต์กํ ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ปค์คํฐ๋ง์ด์ฆ ๊ฐ๋ฅ์ฑ์ ์ ๊ณตํ๋ ๋์ ์์ค์ ์ถ์ํ์ ์ผ๋ฐํ๋ฅผ ์ป์ ์ ์๋ค.
๊ฐ๋ ์ฑ๊ณผ ์ฑ๋ฅ ๋ ๋ง๋ฆฌ ํ ๋ผ๋ฅผ ์ก๊ธฐ ์ํด์๋ ๋ค์ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ํ์ธํ ํ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ๋ฌธ์ ์ ํนํ๋ ํด๊ฒฐ์ฑ ์ ๊ณ ๋ฅด๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค.
์๋ฅผ ๋ค์ด ๋ฉ๋ด ์ ์ฒด์ ์นผ๋ก๋ฆฌ๋ฅผ ๊ณ์ฐํ๋ ์์ ์์๋ (IntStream์ ์ฌ์ฉํ)๊ฐ์ฅ ๋ง์ง๋ง์ ํ์ธํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ๊ฐ๋ ์ฑ์ด ์ข๊ณ ๊ฐ๊ฒฐํ๋ค. ๋ํ IntStream ๋๋ถ์ ์๋ ์ธ๋ฐ์ฑ ์ฐ์ฐ์ ์ํํ๊ฑฐ๋ Integer๋ฅผ int๋ก ๋ณํํ๋ ๊ณผ์ ์ ํผํ ์ ์์ผ๋ฏ๋ก ์ฑ๋ฅ๊น์ง ์ข๋ค.
Quiz. ๋ฆฌ๋์ฑ์ผ๋ก ๋ฌธ์์ด ์ฐ๊ฒฐํ๊ธฐ
์๋ joining ์ปฌ๋ ํฐ๋ฅผ 6.2.3์ ์์ ์ฌ์ฉํ reducing ์ปฌ๋ ํฐ๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ๊พผ ์ฝ๋๋ฅผ ๋ชจ๋ ์ ํํ์์ค.
1 2 3 4 5 6 7 8
String shortMenu = menu.stream().map(Dish::getName).collect(joining()); 1. String shortMenu = menu.stream().map(Dish::getName) .collect(reducing((s1, s2) -> s1 + s2)).get(); 2. String shortMenu = menu.stream() .collect(reducing((d1, d2) -> d1.getName() + d2.getName())).get(); 3. String shortMenu = menu.stream() .collect(reducing("", Dish::getName, (s1, s2) -> s1 + s2));
์ ๋ต
1๋ฒ๊ณผ 3๋ฒ์ด ์ ๋ต์ด๋ฉฐ, 2๋ฒ์ ์ปดํ์ผ๋์ง ์๋ ์ฝ๋๋ค.
- ์๋์ joining ์ปฌ๋ ํฐ์ฒ๋ผ ๊ฐ ์๋ฆฌ๋ฅผ ์๋ฆฌ๋ช ์ผ๋ก ๋ณํํ ๋ค์์ ๋ฌธ์์ด์ ๋์ ์๋ก ์ฌ์ฉํด์ ๋ฌธ์์ด ์คํธ๋ฆผ์ ๋ฆฌ๋์คํ๋ฉด์ ์๋ฆฌ๋ช ์ ํ๋์ฉ ์ฐ๊ฒฐํ๋ค.
- reducing์ BinaryOperator
, ์ฆ BiFunction<T, T, T>๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค. ์ฆ, reducing์ ๋ ์ธ์๋ฅผ ๋ฐ์ **๊ฐ์ ํ์์ ๋ฐํ**ํ๋ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค. ํ์ง๋ง 2๋ฒ ๋๋ค ํํ์์ ๋ ๊ฐ์ ์๋ฆฌ๋ฅผ ์ธ์๋ก ๋ฐ์ ๋ฌธ์์ด์ ๋ฐํํ๋ค. - ๋น ๋ฌธ์์ด์ ํฌํจํ๋ ๋์ ์๋ฅผ ์ด์ฉํด์ ๋ฆฌ๋์ฑ ๊ณผ์ ์ ์์ํ๋ฉฐ, ์คํธ๋ฆผ์ ์๋ฆฌ๋ฅผ ๋ฐฉ๋ฌธํ๋ฉด์ ๊ฐ ์๋ฆฌ๋ฅผ ์๋ฆฌ๋ช ์ผ๋ก ๋ณํํ ๋ค์์ ๋์ ์๋ก ์ถ๊ฐํ๋ค. ์ธ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ reducing์ ๋์ ์ ์ด๊น๊ฐ์ ์ค์ ํ ์ ์์ผ๋ฏ๋ก Optional์ ๋ฐํํ ํ์๊ฐ ์๋ค.
1๋ฒ๊ณผ 3๋ฒ์ด ์ฌ๋ฐ๋ฅธ ์ฝ๋์ด๊ธด ํ์ง๋ง ๋จ์ง ๋ฒ์ฉ reducing์ผ๋ก joining์ ๊ตฌํํ ์ ์์์ ๋ณด์ฌ์ฃผ๋ ์์ ์ผ ๋ฟ์ด๋ค. ์ค๋ฌด์์๋ joining์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ๋ ์ฑ๊ณผ ์ฑ๋ฅ์ ์ข๋ค.
6.3 ๊ทธ๋ฃนํ
๋ฐ์ดํฐ ์งํฉ์ ํ๋ ์ด์์ ํน์ฑ์ผ๋ก ๋ถ๋ฅํด์ ๊ทธ๋ฃนํํ๋ ์ฐ์ฐ
์๋ฐ 8์ ํจ์ํ์ ์ด์ฉํ๋ฉด ๊ฐ๋ ์ฑ ์๋ ํ ์ค์ ์ฝ๋๋ก ๊ทธ๋ฃนํ๋ฅผ ๊ตฌํํ ์ ์๋ค.
๋ฉ๋ด ๊ทธ๋ฃนํ ์์ (๊ณ ๊ธฐ๋ฅผ ํฌํจํ๋ ๊ทธ๋ฃน, ์์ ์ ํฌํจํ๋ ๊ทธ๋ฃน, ๋๋จธ์ง ๊ทธ๋ฃน)
๋ค์์ฒ๋ผ ํฉํ ๋ฆฌ ๋ฉ์๋ Collectors.groupingBy๋ฅผ ์ด์ฉํด์ ์ฝ๊ฒ ๋ฉ๋ด๋ฅผ ๊ทธ๋ฃนํํ ์ ์๋ค.
1
2
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
๋ค์์ Map์ ํฌํจ๋ ๊ฒฐ๊ณผ๋ค.
1
2
{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza],
MEAT=[pork, beef, chicken]}
์คํธ๋ฆผ์ ๊ฐ ์๋ฆฌ์์ Dish.Type๊ณผ ์ผ์นํ๋ ๋ชจ๋ ์๋ฆฌ๋ฅผ ์ถ์ถํ๋ ํจ์๋ฅผ groupingBy ๋ฉ์๋๋ก ์ ๋ฌํ๋ค. ์ด ํจ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์คํธ๋ฆผ์ด ๊ทธ๋ฃนํ๋๋ฏ๋ก ์ด๋ฅผ ๋ถ๋ฅ ํจ์๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์์ ) 400์นผ๋ก๋ฆฌ ์ดํ๋ฅผ โdietโ๋ก, 400~700 ์นผ๋ก๋ฆฌ๋ฅผ โnormalโ๋ก, 700 ์นผ๋ก๋ฆฌ ์ด๊ณผ๋ฅผ โfatโ ์๋ฆฌ๋ก ๋ถ๋ฅ
Dish ํด๋์ค์๋ ์ด๋ฌํ ์ฐ์ฐ์ ํ์ํ ๋ฉ์๋๊ฐ ์์ผ๋ฏ๋ก ๋ฉ์๋ ์ฐธ์กฐ ๋์ ๋๋ค ํํ์์ผ๋ก ํ์ํ ๋ก์ง์ ๊ตฌํํ ์ ์๋ค.
1
2
3
4
5
6
7
8
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 ๊ทธ๋ฃนํ๋ ์์ ์กฐ์
์์๋ฅผ ๊ทธ๋ฃนํ ํ ๋ค์์๋ ๊ฐ ๊ฒฐ๊ณผ ๊ทธ๋ฃน์ ์์๋ฅผ ์กฐ์ํ๋ ์ฐ์ฐ์ด ํ์ํ๋ค.
ex) 500 ์นผ๋ก๋ฆฌ๊ฐ ๋๋ ์๋ฆฌ๋ง ํํฐ๋งํ๋ค๊ณ ๊ฐ์ ํ์. ๋ค์ ์ฝ๋์ฒ๋ผ ๊ทธ๋ฃนํ ํ๊ธฐ ์ ์ ํ๋ ๋์ผ์ดํธ๋ก ํํฐ๋ฅผ ์ ์ฉํด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค ์๊ฐํ ๊ฒ์ด๋ค.
1
2
3
Map<Dish.Type, List<Dish>> caloricDishesByType =
menu.stream().filter(dish -> dish.getCalories() > 500)
.collect(groupingBy(Dish::getType));
์ ์ฝ๋๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ง๋ง ๋จ์ ๋ ์กด์ฌํ๋ค. ๋ฉ๋ด ์๋ฆฌ๋ ๋ค์์ฒ๋ผ ๋งต ํํ๋ก ๋์ด ์์ผ๋ฏ๋ก ์ฝ๋์ ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด ๋งต์ ์ฝ๋๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
1
{OTHER=[french fries, pizza], MEAT=[pork, beef]}
โ ํํฐ ํ๋ ๋์ผ์ดํธ๋ฅผ ๋ง์กฑํ๋ FISH ์ข ๋ฅ ์๋ฆฌ๋ ์์ผ๋ฏ๋ก ๊ฒฐ๊ณผ ๋งต์์ ํด๋น ํค ์์ฒด๊ฐ ์ฌ๋ผ์ง๋ค!
Collectors ํด๋์ค์ groupingBy ํฉํ ๋ฆฌ ๋ฉ์๋์ ๋ ๋ฒ์งธ ์ธ์๋ฅผ ๊ฐ์ง๊ณ ์ด๋ฅผ ํด๊ฒฐํ ์ ์๋ค. ๋ ๋ฒ์งธ Collector ์์ผ๋ก ํํฐ ํ๋ ๋์ผ์ดํธ๋ฅผ ์ด๋์ํจ๋ค.
1
2
3
4
Map<Dish.Type, List<Dish>> caloricDishesByType =
menu.stream()
.collect(groupingBy(Dish::getType,
filtering(dish -> dish.getCalories() > 500, toList())));
์ด๋ ๊ฒ ๋๋ฉด ๋ชฉ๋ก์ด ๋น์ด์๋ FISH๋ ํญ๋ชฉ์ผ๋ก ์ถ๊ฐ๋๋ค.
1
{OTHER=[french fries, pizza], MEAT=[pork, beef], FISH=[]}
ex) ๊ทธ๋ฃน์ ๊ฐ ์๋ฆฌ๋ฅผ ๊ด๋ จ ์ด๋ฆ ๋ชฉ๋ก์ผ๋ก ๋ณํ
๋งคํ ํจ์๋ฅผ ์ด์ฉํด ์์๋ฅผ ๋ณํํด์ค๋ค. filtering ์ปฌ๋ ํฐ์ฒ๋ผ mapping ๋ฉ์๋๋ฅผ groupingBy์ ์ธ์๋ก ์ฌ์ฉํ ์ ์๋ค.
1
2
3
Map<Dish.Type, **List<String>**> dishNamesByType =
menu.stream()
.collect(groupingBy(Dish::getType, mapping(Dish::getName, toList())));
์ด์ ์์ ์ ๋ฌ๋ฆฌ ๊ฒฐ๊ณผ ๋งต์ ๊ฐ ๊ทธ๋ฃน์ ์๋ฆฌ๊ฐ ์๋๋ผ ๋ฌธ์์ด ๋ฆฌ์คํธ์ด๋ค.
groupingBy์ ์ฐ๊ณํด ์ธ ๋ฒ์งธ ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํด์ ์ผ๋ฐ ๋งต์ด ์๋ flatMap ๋ณํ์ ์ํํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
Map<String, List<String>> dishTags = new HashMap<>();
dishTags.put("pork", asList("greasy", "salty"));
dishTags.put("beef", asList("salty", "roasted"));
dishTags.put("chicken", asList("fried", "crisp"));
dishTags.put("french fries", asList("greasy", "fries"));
dishTags.put("rice", asList("light", "natural"));
dishTags.put("season fruit", asList("fresh", "natural"));
dishTags.put("pizza", asList("tasty", "salty"));
dishTags.put("prawns", asList("tasty", "roasted"));
dishTags.put("salmon", asList("delicious", "fresh"));
์ ํ๊ทธ ๋ชฉ๋ก์ ๊ฐ์ง ๊ฐ ์๋ฆฌ๋ก ๊ตฌ์ฑ๋ ๋งต์์ flatMapping ์ปฌ๋ ํฐ๋ฅผ ์ด์ฉํ๋ฉด ๊ฐ ํ์์ ์๋ฆฌ์ ํ๊ทธ๋ฅผ ๊ฐํธํ๊ฒ ์ถ์ถํ ์ ์๋ค.
1
2
3
4
Map<Dish.Type, Set<String>> dishNamesByType =
menu.stream()
.collect(grouping(Dish::getType,
flatMapping(dish -> dishTags.get(dish.getName()).stream(), toSet())));
๊ฐ ์๋ฆฌ์์ ํ๊ทธ ๋ฆฌ์คํธ๋ฅผ ์ป์ด์ผ ํ๋ค. ๋ ์์ค์ ๋ฆฌ์คํธ๋ฅผ ํ๋ฉดํํ๋ ค๋ฉด flatMap์ ์ํํด์ผ ํ๋ค.
1
2
{MEAT=[salty, greasy, roasted, fried, crisp], FISH=[roasted, tasty, fresh, delicious],
OTHER=[salty, greasy, natural, light, tasty, fresh, fried]}
6.3.2 ๋ค์์ค ๊ทธ๋ฃนํ
๋ ์ธ์๋ฅผ ๋ฐ๋ ํฉํ ๋ฆฌ ๋ฉ์๋ Collectors.groupingBy๋ฅผ ์ด์ฉํด์ ํญ๋ชฉ์ ๋ค์์ค์ผ๋ก ๊ทธ๋ฃนํํ ์ ์๋ค.
groupingBy๋ฅผ ์ค์ฒฉํ์ฌ ๋ฐ๊นฅ์ชฝ groupingBy ๋ฉ์๋์ ์คํธ๋ฆผ์ ํญ๋ชฉ์ ๋ถ๋ฅํ ๋ ๋ฒ์งธ ๊ธฐ์ค์ ์ ์ํ๋ ๋ด๋ถ groupingBy๋ฅผ ์ ๋ฌํ์ฌ ๋ ์์ค์ผ๋ก ์คํธ๋ฆผ์ ํญ๋ชฉ์ ๊ทธ๋ฃนํํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(Dish::getType, // ์ฒซ ๋ฒ์งธ ์์ค์ ๋ถ๋ฅ ํจ์
grouping(dish -> { // ๋ ๋ฒ์งธ ์์ค์ ๋ถ๋ฅ ํจ์
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
})
)
);
๊ทธ๋ฃนํ์ ๊ฒฐ๊ณผ๋ก ๋ค์๊ณผ ๊ฐ์ ๋ ์์ค์ ๋งต์ด ๋ง๋ค์ด์ง๋ค.
1
2
3
{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
FISH={DIET=[prawns], NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
์ฒซ ๋ฒ์งธ ์์ค์ ๋ถ๋ฅ ํจ์
๋ถ๋ฅ ํค ๊ฐ : fish, meat, other
๋ ๋ฒ์งธ ์์ค์ ๋ถ๋ฅ ํจ์
๋ถ๋ฅ ํค ๊ฐ : normal, diet, fat
์ต์ข
- ์ฒซ ๋ฒ์งธ ํค์ ๋ ๋ฒ์งธ ํค์ ๊ธฐ์ค์ ๋ถํฉํ๋ ์์ ๋ฆฌ์คํธ ๊ฐ : salmon, pizza ๋ฑ
๋ค์์ค ๊ทธ๋ฃนํ ์ฐ์ฐ์ ๋ค์ํ ์์ค์ผ๋ก ํ์ฅ ๊ฐ๋ฅํ๋ค. n์์ค ๊ทธ๋ฃนํ์ ๊ฒฐ๊ณผ๋ n์์ค ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ํํ๋๋ n์์ค ๋งต์ด ๋๋ค.
6.3.3 ์๋ธ๊ทธ๋ฃน์ผ๋ก ๋ฐ์ดํฐ ์์ง
groupingBy ์ปฌ๋ ํฐ์ ๋ ๋ฒ์งธ ์ธ์๋ก counting ์ปฌ๋ ํฐ๋ฅผ ์ ๋ฌํด์ ๋ฉ๋ด์์ ์๋ฆฌ์ ์๋ฅผ ์ข ๋ฅ๋ณ๋ก ๊ณ์ฐํ ์ ์๋ค.
1
2
Map<Dish.Type, Long> typesCount =
menu.stream().collect(groupingBy(Dish::getType, counring()));
๋ค์์ ๊ฒฐ๊ณผ ๋งต์ด๋ค.
1
{MEAT=3, FISH=2, OTHER=4}
๋ถ๋ฅ ํจ์ ํ ๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋ grouping(f)๋ ์ฌ์ค groupingBy(f, toList())์ ์ถ์ฝํ์ด๋ค.
์๋ฆฌ์ ์ข ๋ฅ๋ฅผ ๋ถ๋ฅํ๋ ์ปฌ๋ ํฐ๋ก ๋ฉ๋ด์์ ๊ฐ์ฅ ๋์ ์นผ๋ก๋ฆฌ๋ฅผ ๊ฐ์ง ์๋ฆฌ๋ฅผ ์ฐพ๋ ํ๋ก๊ทธ๋จ๋ ๋ค์ ๊ตฌํํ ์ ์๋ค.
1
2
3
4
Map<Dish.Type, Optional<Dish>> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories))));
โ ์๋ฆฌ์ ์ข
๋ฅ(key), Oprional
1
{FISH=Optional[salmon], OTHER=Oprional[pizza], MEAT=Optional[pork]}
์ปฌ๋ ํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅธ ํ์์ ์ ์ฉํ๊ธฐ
์ด์ ๊ทธ๋ฃนํ ์ฐ์ฌ์์ ๋งต์ ๋ชจ๋ ๊ฐ์ Optional๋ก ๊ฐ์ ํ์๊ฐ ์์ผ๋ฏ๋ก Optional์ ์ญ์ ํ ์ ์๋ค.
Collectors.collectingAndThen์ผ๋ก ์ปฌ๋ ํฐ๊ฐ ๋ฐํํ ๊ฒฐ๊ณผ๋ฅผ ๋ค๋ฅธ ํ์์ผ๋ก ํ์ฉํ ์ ์๋ค.
1
2
3
4
5
6
Map<Dish.Type, Dish> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType, // ๋ถ๋ฅ ํจ์
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)), // ๊ฐ์ธ์ธ ์ปฌ๋ ํฐ
Optional::get))); // ๋ณํ ํจ์
Optional::get์ผ๋ก ๋ฐํ๋ Optional์ ํฌํจ๋ ๊ฐ์ ์ถ์ถํ๋ค. ๋ฆฌ๋์ฑ ์ปฌ๋ ํฐ๋ ์ ๋ Optional.empty()๋ฅผ ๋ฐํํ์ง ์์ผ๋ฏ๋ก ์์ ํ ์ฝ๋๋ค. ๋ค์์ ๋งต์ ๊ฒฐ๊ณผ๋ค.
1
{FISH=salmon, OTHER=pizza, MEAT=pork}
- ์ค์ฒฉ ์ปฌ๋ ํฐ์ ๋์(๊ฐ์ฅ ์ธ๋ถ ๊ณ์ธต์์ ์์ชฝ์ผ๋ก ์์
์ํ)
- ์ปฌ๋ ํฐ๋ ์ ์ ์ผ๋ก ํ์๋์ด ์์ผ๋ฉฐ groupingBy๋ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ ์์นํ๋ฉด์ ์๋ฆฌ์ ์ข ๋ฅ์ ๋ฐ๋ผ ๋ฉ๋ด ์คํธ๋ฆผ์ ์ธ ๊ฐ์ ์๋ธ์คํธ๋ฆผ์ผ๋ก ๊ทธ๋ฃนํํ๋ค.
- groupingBy ์ปฌ๋ ํฐ๋ collectingAndThen ์ปฌ๋ ํฐ๋ฅผ ๊ฐ์ผ๋ค. ๋ฐ๋ผ์ ๋ ๋ฒ์งธ ์ปฌ๋ ํฐ๋ ๊ทธ๋ฃนํ๋ ์ธ ๊ฐ์ ์๋ธ์คํธ๋ฆผ์ ์ ์ฉ๋๋ค.
- collectingAndThen ์ปฌ๋ ํฐ๋ ์ธ ๋ฒ์งธ ์ปฌ๋ ํฐ maxBy๋ฅผ ๊ฐ์ผ๋ค.
- ๋ฆฌ๋์ฑ ์ปฌ๋ ํฐ๊ฐ ์๋ธ์คํธ๋ฆผ์ ์ฐ์ฐ์ ์ํํ ๊ฒฐ๊ณผ์ collectingAndThen์ Optional::get ๋ณํ ํจ์๊ฐ ์ ์ฉ๋๋ค.
- groupingBy ์ปฌ๋ ํฐ๊ฐ ๋ฐํํ๋ ๋งต์ ๋ถ๋ฅ ํค์ ๋์ํ๋ ์ธ ๊ฐ์ด ๊ฐ๊ฐ์ ์๋ฆฌ ํ์์์ ๊ฐ์ฅ ๋์ ์นผ๋ก๋ฆฌ๋ค.
groupingBy์ ํจ๊ป ์ฌ์ฉํ๋ ๋ค๋ฅธ ์ปฌ๋ ํฐ ์์
์ผ๋ฐ์ ์ผ๋ก ์คํธ๋ฆผ์์ ๊ฐ์ ๊ทธ๋ฃน์ผ๋ก ๋ถ๋ฅ๋ ๋ชจ๋ ์์์ ๋ฆฌ๋์ฑ ์์ ์ ์ํํ ๋๋ ํฉํ ๋ฆฌ ๋ฉ์๋ groupingBy์ ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํ๋ค.
ex) ๋ฉ๋ด์ ์๋ ๋ชจ๋ ์๋ฆฌ์ ์นผ๋ก๋ฆฌ ํฉ๊ณ๋ฅผ ๊ตฌํ๋ ค๊ณ ๋ง๋ ์ปฌ๋ ํฐ ์ฌ์ฌ์ฉ
1
2
3
Map<Dish.Type, Integer> totalCaloriesByType =
menu.stream().collect(groupingBy(Dish::getType,
summingInt(Dish::getCalories)));
์ด์ธ์๋ mapping ๋ฉ์๋๋ก ๋ง๋ค์ด์ง ์ปฌ๋ ํฐ๋ groupingBy์ ์์ฃผ ์ฌ์ฉ๋๋ค.
mapping ๋ฉ์๋
- ์ธ์: ์คํธ๋ฆผ์ ์ธ์๋ฅผ ๋ณํํ๋ ํจ์, ๋ณํ ํจ์์ ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ฅผ ๋์ ํ๋ ์ปฌ๋ ํฐ
- ์ ๋ ฅ ์์๋ฅผ ๋์ ํ๊ธฐ ์ ์ ๋งคํ ํจ์๋ฅผ ์ ์ฉํด์ ๋ค์ํ ํ์์ ๊ฐ์ฒด๋ฅผ ์ฃผ์ด์ง ํ์์ ์ปฌ๋ ํฐ์ ๋ง๊ฒ ๋ณํ
ex) ๊ฐ ์๋ฆฌ ํ์์ ์กด์ฌํ๋ ๋ชจ๋ CaloricLevel ๊ฐ์ ์๊ณ ์ถ์ ๋, groupingBy์ mapping ์ปฌ๋ ํฐ๋ฅผ ํฉ์ณ ๊ธฐ๋ฅ ๊ตฌํ ๊ฐ๋ฅ
1
2
3
4
5
6
7
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())));
mapping ๋ฉ์๋์ ์ ๋ฌํ ๋ณํ ํจ์๋ Dish๋ฅผ CaloricLevel๋ก ๋งคํํ๋ค. CaloricLevel ๊ฒฐ๊ณผ ์คํธ๋ฆผ์ toSet์ปฌ๋ ํฐ๋ก ์ ๋ฌ๋๋ฉด์ ๋ฆฌ์คํธ๊ฐ ์๋ ์งํฉ์ผ๋ก ์คํธ๋ฆผ์ ์์๊ฐ ๋์ ๋๋ค(์ค๋ณต๊ฐ ์ ์ฅx)
1
{OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], FISH=[DIET, NORMAL]}
Set์ ํ์์ด ์ ํด์ ธ ์์ง ์๋ค. ์ด๋ toCollection์ ์ด์ฉํ๋ฉด ์ํ๋ ๋ฐฉ์์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ ์ดํ ์ ์๋ค.
์๋ฅผ ๋ค์ด ๋ค์์ฒ๋ผ ๋ฉ์๋ ์ฐธ์กฐ HashSet::new๋ฅผ toCollection์ ์ ๋ฌํ ์ ์๋ค.
1
2
3
4
5
6
7
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; }
toCollection(HashSet::new))));
6.4 ๋ถํ
: ๋ถํ ํจ์๋ผ ๋ถ๋ฆฌ๋ ํ๋ ๋์ผ์ดํธ๋ฅผ ๋ถ๋ฅ ํจ์๋ก ์ฌ์ฉํ๋ ํน์ํ ๊ทธ๋ฃนํ ๊ธฐ๋ฅ
๋ถํ ํจ์๋ Boolean์ ๋ฐํํ๋ฏ๋ก ๋งต์ ํค ํ์์ Boolean์ด๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ทธ๋ฃนํ ๋งต์ ์ต๋ (์ฐธ ์๋๋ฉด ๊ฑฐ์ง์ ๊ฐ์ ๊ฐ๋) ๋ ๊ฐ์ ๊ทธ๋ฃน์ผ๋ก ๋ถ๋ฅ๋๋ค.
ex) ๋ชจ๋ ์๋ฆฌ๋ฅผ ์ฑ์ ์๋ฆฌ์ ์ฑ์์ด ์๋ ์๋ฆฌ๋ก ๋ถ๋ฅ
1
2
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian)); // ๋ถํ ํจ์
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋งต์ด ๋ฐํ๋๋ค.
1
2
{false=[pork, beef, chicken, prawns, salmon],
true=[french fries, rice, season fruit, pizza]}
์ด์ ์ฐธ๊ฐ์ ํค๋ก ๋งต์์ ๋ชจ๋ ์ฑ์ ์๋ฆฌ๋ฅผ ์ป์ ์ ์๋ค.
1
List<Dish> vegetarianDishes = partitionedMenu.get(true);
๋๋ ๋ฉ๋ด ๋ฆฌ์คํธ๋ก ์์ฑํ ์คํธ๋ฆผ์ ํ๋ ๋์ผ์ดํธ๋ก ํํฐ๋งํ ๋ค์์ ๋ณ๋์ ๋ฆฌ์คํธ์ ๊ฒฐ๊ณผ๋ฅผ ์์งํด๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
1
2
List<Dish> vegetarianDishes =
menu.stream().filter(Dish::isVegetarian).collect(toList());
6.4.1 ๋ถํ ์ ์ฅ์
๋ถํ ํจ์๊ฐ ๋ฐํํ๋ ์ฐธ, ๊ฑฐ์ง์ ๋ ๊ฐ์ง ์์์ ์คํธ๋ฆผ ๋ฆฌ์คํธ๋ฅผ ๋ชจ๋ ์ ์งํ๋ค๋ ๊ฒ์ด ๋ถํ ์ ์ฅ์ ์ด๋ค.
1
2
3
4
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType =
menu.stream()
.collect(partitioningBy(Dish::isVegetarian, // ๋ถํ ํจ์
groupingBy(Dish::getType))); // ๋ ๋ฒ์งธ ์ปฌ๋ ํฐ
์ปฌ๋ ํฐ๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ์ ์๋ ์ค๋ฒ๋ก๋๋ ๋ฒ์ ์ partitioningBy ๋ฉ์๋์ด๋ค.
1
2
{false=[FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
true={OTHER=[french fries, rice, season fruit, pizza]}}
๊ฒฐ๊ณผ์์ ํ์ธํ ์ ์๋ ๊ฒ์ฒ๋ผ ์ฑ์ ์๋ฆฌ์ ์คํธ๋ฆผ๊ณผ ์ฑ์์ด ์๋ ์๋ฆฌ์ ์คํธ๋ฆผ์ ๊ฐ๊ฐ ์๋ฆฌ ์ข ๋ฅ๋ก ๊ทธ๋ฃนํํด์ ๋ ์์ค์ ๋งต์ด ๋ฐํ๋์๋ค.
ex) ์ฑ์ ์๋ฆฌ์ ์ฑ์์ด ์๋ ์๋ฆฌ ๊ฐ๊ฐ์ ๊ทธ๋ฃน์์ ๊ฐ์ฅ ์นผ๋ก๋ฆฌ๊ฐ ๋์ ์๋ฆฌ ์ฐพ๊ธฐ
1
2
3
4
5
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
collectAndThen(maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
๋ค์์ ํ๋ก๊ทธ๋จ ์คํ ๊ฒฐ๊ณผ๋ค.
1
{false=pork, true=pizza}
Quiz. partitioningBy ์ฌ์ฉ
groupingBy ์ปฌ๋ ํฐ์ ๋ง์ฐฌ๊ฐ์ง๋ก partitioningBy ์ปฌ๋ ํฐ๋ ๋ค๋ฅธ ์ปฌ๋ ํฐ์ ์กฐํฉํด์ ์ฌ์ฉํ ์ ์๋ค. ํนํ ๋ ๊ฐ์ partitioningBy ์ปฌ๋ ํฐ๋ฅผ ์ด์ฉํด์ ๋ค์์ค ๋ถํ ์ ์ํํ ์ ์๋ค. ๋ค์ ์ฝ๋์ ๋ค์์ค ๋ถํ ๊ฒฐ๊ณผ๋ฅผ ์์ธกํด๋ณด์.
1 2 3 4 5 6
1. menu.stream().collect(partitioningBy(Dish::isVegetarian, partitioningBy(d -> d.getCalories() > 500))); 2. menu.stream().collect(partitioningBy(Dish::isVegetarian, partitioningBy(Dish::getType))); 3. menu.stream().collect(partitioningBy(Dish::isVegetarian, counting()));
- ์ ๋ต
์ ํจํ ๋ค์์ค ๋ถํ ์ฝ๋๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ ๋ ์์ค์ ๋งต์ ๋ฐํํ๋ค.
1 2
{false={false=[chicken, prawns, salmon], true=[pork, beef]}, true={false=[rice, season fruit], true=[french fries, pizza]}}
- partitioningBy๋ ๋ถ๋ฆฌ์ธ์ ๋ฐํํ๋ ํจ์, ์ฆ ํ๋ ๋์ผ์ดํธ๋ฅผ ์๊ตฌํ๋ฏ๋ก ์ปดํ์ผ๋์ง ์๋ ์ฝ๋๋ค. Dish::getType์ ํ๋ ๋์ผ์ดํธ๋ก ์ฌ์ฉํ ์ ์๋ ๋ฉ์๋ ์ฐธ์กฐ๋ค.
๋ถํ ๋ ๊ฐ ํญ๋ชฉ์ ๊ฐ์๋ฅผ ๊ณ์ผํ๋ ์ฝ๋๋ก ๋ค์๊ณผ ๊ฐ์ ๋งต์ ๋ฐํํ๋ค.
1
{false=5, true=4}
- ์ ๋ต
6.4.2 ์ซ์๋ฅผ ์์์ ๋น์์๋ก ๋ถํ ํ๊ธฐ
์ ์ n์ ์ธ์๋ก ๋ฐ์์ 2์์ n๊น์ง์ ์์ฐ์๋ฅผ ์์์ ๋น์์๋ก ๋๋๋ ํ๋ก๊ทธ๋จ์ ๊ตฌํํ์. ๋จผ์ ์ฃผ์ด์ง ์๊ฐ ์์์ธ์ง ์๋์ง ํ๋จํ๋ ํ๋ ๋์ผ์ดํธ๋ฅผ ๊ตฌํํ๋ฉด ํธ๋ฆฌํ ๊ฒ์ด๋ค.
1
2
3
4
public boolean **isPrime**(int candidate) {
return IntStream.range(2, candidate) // 2๋ถํฐ candidate ๋ฏธ๋ง ์ฌ์ด์ ์์ฐ์ ์์ฑ
.noneMatch(i -> candidate % i == 0); // ์คํธ๋ฆผ์ ๋ชจ๋ ์ ์๋ก candidate๋ฅผ ๋๋ ์ ์์ผ๋ฉด ์ฐธ์ ๋ฐํ
}
๋ค์์ฒ๋ผ ์์์ ๋์์ ์ฃผ์ด์ง ์์ ์ ๊ณฑ๊ทผ ์ดํ์ ์๋ก ์ ํํ ์ ์๋ค.
1
2
3
4
5
public boolean **isPrime**(int candidate) {
int candidateRoot = (int) Math.sqrt((double)candidate);
return IntStream.rangeClosed(2, candidateRoot)
.noneMatch(i -> candidate % i == 0);
}
์ด์ n๊ฐ์ ์ซ์๋ฅผ ํฌํจํ๋ ์คํธ๋ฆผ์ ๋ง๋ ๋ค์์ ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ isPrime ๋ฉ์๋๋ฅผ ํ๋ ๋์ผ์ดํธ๋ก ์ด์ฉํ๊ณ partitioningBy ์ปฌ๋ ํฐ๋ก ๋ฆฌ๋์คํด์ ์ซ์๋ฅผ ์์์ ๋น์์๋ก ๋ถ๋ฅํ ์ ์๋ค.
1
2
3
4
public Map<Boolean, List<Integer>> partitionPrimes(int n) {
return IntStream.rangeClosed(2, n).boxed()
.collect(partitioningBy(candidate -> isPrime(candidate)));
}
6.5 Collector ์ธํฐํ์ด์ค
Collector ์ธํฐํ์ด์ค๋ฅผ ์ง์ ๊ตฌํํด์ ๋ ํจ์จ์ ์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ์ปฌ๋ ํฐ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด์.
๋ค์ ์ฝ๋๋ Collector ์ธํฐํ์ด์ค์ ์๊ทธ๋์ฒ์ ๋ค์ฏ ๊ฐ์ ๋ฉ์๋ ์ ์๋ฅผ ๋ณด์ฌ์ค๋ค.
1
2
3
4
5
6
7
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
1
public class ToListCollector<T> implements Collector<T, List<T>, List<T>>
๋์ ๊ณผ์ ์์ ์ฌ์ฉ๋๋ ๊ฐ์ฒด๊ฐ ์์ง ๊ณผ์ ์ ์ต์ข ๊ฒฐ๊ณผ๋ก ์ฌ์ฉ๋๋ค.
6.5.1 Collector ์ธํฐํ์ด์ค์ ๋ฉ์๋ ์ดํด๋ณด๊ธฐ
Colllector ์ธํฐํ์ด์ค์ ์ ์๋ ๋ค์ฏ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ดํด๋ณด์.
๋จผ์ ์ดํด๋ณผ ๋ค ๊ฐ์ ๋ฉ์๋๋ collect ๋ฉ์๋์์ ์คํํ๋ ํจ์๋ฅผ ๋ฐํํ๋ ๋ฐ๋ฉด, ๋ค์ฏ ๋ฒ์งธ ๋ฉ์๋ characteristics๋ collect ๋ฉ์๋๊ฐ ์ด๋ค ์ต์ ํ(ex. ๋ณ๋ ฌํ)๋ฅผ ์ด์ฉํด์ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ํํ ๊ฒ์ธ์ง ๊ฒฐ์ ํ๋๋ก ๋๋ ํํธ ํน์ฑ ์งํฉ์ ์ ๊ณตํ๋ค.
supplier ๋ฉ์๋ : ์๋ก์ด ๊ฒฐ๊ณผ ์ปจํ ์ด๋ ๋ง๋ค๊ธฐ
supplier ๋ฉ์๋๋ ๋น ๊ฒฐ๊ณผ๋ก ์ด๋ฃจ์ด์ง Supplier๋ฅผ ๋ฐํํด์ผ ํ๋ค.
์ฆ, supplier๋ ์์ง ๊ณผ์ ์์ ๋น ๋์ ์ ์ธ์คํด์ค๋ฅผ ๋ง๋๋ ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ํจ์๋ค.
ToListCollector์ฒ๋ผ ๋์ ์๋ฅผ ๋ฐํํ๋ ์ปฌ๋ ํฐ์์๋ ๋น ๋์ ์๊ฐ ๋น์ด์๋ ์คํธ๋ฆผ์ ์์ง ๊ณผ์ ์ ๊ฒฐ๊ณผ๊ฐ ๋ ์ ์๋ค. ToListCollector์์ supplier๋ ๋ค์์ฒ๋ผ ๋น ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค.
1
2
3
public Supplier<List<T>> supplier() {
return () -> new ArrayList<T>();
}
์์ฑ์ ์ฐธ์กฐ๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
1
2
3
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
accumulator ๋ฉ์๋ : ๊ฒฐ๊ณผ ์ปจํ ์ด๋์ ์์ ์ถ๊ฐํ๊ธฐ
accumulator ๋ฉ์๋๋ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์ํํ๋ ํจ์๋ฅผ ๋ฐํํ๋ค. ์คํธ๋ฆผ์์ n๋ฒ์งธ ์์๋ฅผ ํ์ํ ๋ ๋ ์ธ์, ์ฆ ๋์ ์(์คํธ๋ฆผ์ ์ฒซ n-1๊ฐ ํญ๋ชฉ์ ์์งํ ์ํ)์ n๋ฒ์งธ ์์๋ฅผ ํจ์์ ์ ์ฉํ๋ค. ํจ์์ ๋ฐํ๊ฐ์ void, ์ฆ ์์๋ฅผ ํ์ํ๋ฉด์ ์ ์ฉํ๋ ํจ์์ ์ํด ๋์ ์ ๋ด๋ถ์ํ๊ฐ ๋ฐ๋๋ฏ๋ก ๋์ ์๊ฐ ์ด๋ค ๊ฐ์ผ์ง ๋จ์ ํ ์ ์๋ค.
ToListCollector์์ accumulator๊ฐ ๋ฐํํ๋ ํจ์๋ ์ด๋ฏธ ํ์ํ ํญ๋ชฉ์ ํฌํจํ๋ ๋ฆฌ์คํธ์ ํ์ฌ ํญ๋ชฉ์ ์ถ๊ฐํ๋ ์ฐ์ฐ์ ์ํํ๋ค.
1
2
3
public BiConsumer<List<T>, T> accumulator() {
return (list, item) -> list.add(item);
}
๋ค์์ฒ๋ผ ๋ฉ์๋ ์ฐธ์กฐ๋ฅผ ์ด์ฉํ๋ฉด ์ฝ๋๊ฐ ๋ ๊ฐ๊ฒฐํด์ง๋ค.
1
2
3
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
finisher ๋ฉ์๋ : ์ต์ข ๋ณํ๊ฐ์ ๊ฒฐ๊ณผ ์ปจํ ์ด๋๋ก ์ ์ฉํ๊ธฐ
finisher ๋ฉ์๋๋ ์คํธ๋ฆผ ํ์์ ๋๋ด๊ณ ๋์ ์ ๊ฐ์ฒด๋ฅผ ์ต์ข ๊ฒฐ๊ณผ๋ก ๋ณํํ๋ฉด์ ๋์ ๊ณผ์ ์ ๋๋ผ ๋ ํธ์ถํ ํจ์๋ฅผ ๋ฐํํด์ผ ํ๋ค. ๋๋ก๋ ToListCollector์์ ๋ณผ ์ ์๋ ๊ฒ์ฒ๋ผ ๋์ ์ ๊ฐ์ฒด๊ฐ ์ด๋ฏธ ์ต์ข ๊ฒฐ๊ณผ์ธ ์ํฉ๋ ์๋ค. ์ด๋ฐ ๋๋ ๋ณํ ๊ณผ์ ์ด ํ์ํ์ง ์์ผ๋ฏ๋ก finisher ๋ฉ์๋๋ ํญ๋ฑ ํจ์๋ฅผ ๋ฐํํ๋ค.
1
2
3
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
์ง๊ธ๊น์ง ์ดํด๋ณธ ์ธ ๊ฐ์ง ๋ฉ์๋๋ก๋ ์์ฐจ์ ์คํธ๋ฆผ ๋ฆฌ๋์ฑ ๊ธฐ๋ฅ์ ์ํํ ์ ์๋ค. ์ค์ ๋ก๋ collect๊ฐ ๋์ํ๊ธฐ ์ ์ ๋ค๋ฅธ ์ค๊ฐ ์ฐ์ฐ๊ณผ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฒ์ผ๋ฅธ ํน์ฑ ๊ทธ๋ฆฌ๊ณ ๋ณ๋ ฌ ์คํ ๋ฑ๋ ๊ณ ๋ คํด์ผ ํ๋ฏ๋ก ์คํธ๋ฆผ ๋ฆฌ๋์ฑ ๊ธฐ๋ฅ ๊ตฌํ์ ์๊ฐ๋ณด๋ค ๋ณต์กํ๋ค.
combiner ๋ฉ์๋ : ๋ ๊ฒฐ๊ณผ ์ปจํ ์ด๋ ๋ณํฉ
combiner ๋ฉ์๋๋ ๋ง์ง๋ง์ผ๋ก ๋ฆฌ๋์ฑ ์ฐ์ฐ์์ ์ฌ์ฉํ ํจ์๋ฅผ ๋ฐํํ๋ค.
combiner์ ์คํธ๋ฆผ์ ์๋ก ๋ค๋ฅธ ์๋ธํํธ๋ฅผ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ๋ ๋์ ์๊ฐ ์ด ๊ฒฐ๊ณผ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ ์ํ๋ค. toList์ combiner๋ ๋น๊ต์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค. ์ฆ, ์คํธ๋ฆผ์ ๋ ๋ฒ์งธ ์๋ธ ํํธ์์ ์์งํ ํญ๋ชฉ ๋ฆฌ์คํธ๋ฅผ ์ฒซ ๋ฒ์งธ ์๋ธํํธ ๊ฒฐ๊ณผ ๋ฆฌ์คํธ์ ๋ค์ ์ถ๊ฐํ๋ฉด ๋๋ค.
1
2
3
4
5
6
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
}
}
combiner ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ์คํธ๋ฆผ์ ๋ฆฌ๋์ฑ์ ๋ณ๋ ฌ๋ก ์ํํ ์ ์๋ค.
- ์คํธ๋ฆผ์ ๋ถํ ํด์ผ ํ๋์ง ์ ์ํ๋ ์กฐ๊ฑด์ด ๊ฑฐ์ง์ผ๋ก ๋ฐ๋๊ธฐ ์ ๊น์ง ์๋ ์คํธ๋ฆผ์ ์ฌ๊ท์ ์ผ๋ก ๋ถํ ํ๋ค(๋ณดํต ๋ถ์ฐ๋ ์์ ์ ํฌ๊ธฐ๊ฐ ๋๋ฌด ์์์ง๋ฉด ๋ณ๋ ฌ ์ํ์ ์๋๋ ์์ฐจ ์ํ์ ์๋๋ณด๋ค ๋๋ ค์ง๋ค. ์ฆ, ๋ณ๋ ฌ ์ํ์ ํจ๊ณผ๊ฐ ์์๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ๋ก์ธ์ฑ ์ฝ์ด์ ๊ฐ์๋ฅผ ์ด๊ณผํ๋ ๋ณ๋ ฌ ์์ ์ ํจ์จ์ ์ด์ง ์๋ค.)
- ๊ทธ๋ฆผ์์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ฒ๋ผ ๋ชจ๋ ์๋ธ์คํธ๋ฆผ์ ๊ฐ ์์์ ๋ฆฌ๋์ฑ ์ฐ์ฐ์ ์์ฐจ์ ์ผ๋ก ์ ์ฉํด์ ์๋ธ์คํธ๋ฆผ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
- ๋ง์ง๋ง์๋ ์ปฌ๋ ํฐ์ combiner ๋ฉ์๋๊ฐ ๋ฐํํ๋ ํจ์๋ก ๋ชจ๋ ๋ถ๋ถ๊ฒฐ๊ณผ๋ฅผ ์์ผ๋ก ํฉ์น๋ค. ์ฆ, ๋ถํ ๋ ๋ชจ๋ ์๋ธ์คํธ๋ฆผ์ ๊ฒฐ๊ณผ๋ฅผ ํฉ์น๋ฉด์ ์ฐ์ฐ์ด ์๋ฃ๋๋ค.
Characteristics ๋ฉ์๋
characteristics ๋ฉ์๋๋ ์ปฌ๋ ํฐ์ ์ฐ์ฐ์ ์ ์ํ๋ Characteristics ํ์์ ๋ถ๋ณ ์งํฉ์ ๋ฐํํ๋ค.
์คํธ๋ฆผ์ ๋ณ๋ ฌ๋ก ๋ฆฌ๋์คํ ๊ฒ์ธ์ง ๊ทธ๋ฆฌ๊ณ ๋ณ๋ ฌ๋ก ๋ฆฌ๋์คํ๋ค๋ฉด ์ด๋ค ์ต์ ํ๋ฅผ ์ ํํด์ผ ํ ์ง ํํธ๋ฅผ ์ ๊ณตํ๋ค.
Characteristics๋ ๋ค์ ์ธ ํญ๋ชฉ์ ํฌํจํ๋ ์ด๊ฑฐํ์ด๋ค.
UNORDERED
: ๋ฆฌ๋์ฑ ๊ฒฐ๊ณผ๋ ์คํธ๋ฆผ ์์์ ๋ฐฉ๋ฌธ ์์๋ ๋์ ์์์ ์ํฅ์ ๋ฐ์ง ์๋๋ค.
CONCURRENT
: ๋ค์ค ์ค๋ ๋์์ accumulator ํจ์๋ฅผ ๋์์ ํธ์ถํ ์ ์์ผ๋ฉฐ ์ด ์ปฌ๋ ํฐ๋ ์คํธ๋ฆผ์ ๋ณ๋ ฌ ๋ฆฌ๋์ฑ์ ์ํํ ์ ์๋ค. ์ปฌ๋ ํฐ์ ํ๋๊ทธ์ UNORDERED๋ฅผ ํจ๊ป ์ค์ ํ์ง ์์๋ค๋ฉด ๋ฐ์ดํฐ ์์ค๊ฐ ์ ๋ ฌ๋์ด ์์ง ์์(์ฆ, ์งํฉ์ฒ๋ผ ์์์ ์์๊ฐ ๋ฌด์๋ฏธํ) ์ํฉ์์๋ง ๋ณ๋ ฌ ๋ฆฌ๋์ฑ์ ์ํํ ์ ์๋ค.
IDENTITY_FINISH
: finisher ๋ฉ์๋๊ฐ ๋ฐํํ๋ ํจ์๋ ๋จ์ํ identity๋ฅผ ์ ์ฉํ ๋ฟ์ด๋ฏ๋ก ์ด๋ฅผ ์๋ตํ ์ ์๋ค. ๋ฐ๋ผ์ ๋ฆฌ๋์ฑ ๊ณผ์ ์ ์ต์ข ๊ฒฐ๊ณผ๋ก ๋์ ์ ๊ฐ์ฒด๋ฅผ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค. ๋ํ ๋์ ์ A๋ฅผ ๊ฒฐ๊ณผ R๋ก ์์ ํ๊ฒ ํ๋ณํํ ์ ์๋ค.
ToListCollector์์ ์คํธ๋ฆผ์ ์์๋ฅผ ๋์ ํ๋ ๋ฐ ์ฌ์ฉํ ๋ฆฌ์คํธ๊ฐ ์ต์ข ๊ฒฐ๊ณผ ํ์์ด๋ฏ๋ก ์ถ๊ฐ ๋ณํ์ด ํ์์๋ค.
๋ฐ๋ผ์ ToListCollector๋ IDENTITY_FINISH๋ค. ํ์ง๋ง ๋ฆฌ์คํธ์ ์์๋ ์๊ด์ด ์์ผ๋ฏ๋ก UNORDERED๋ค. ๋ง์ง๋ง์ผ๋ก ToListCollector๋ CONCURRENT๋ค.
์์์ ์์๊ฐ ๋ฌด์๋ฏธํ ๋ฐ์ดํฐ ์์ค์ฌ์ผ ๋ณ๋ ฌ๋ก ์คํํ ์ ์๋ค.
6.5.2 ์์ฉํ๊ธฐ
์ปค์คํ ToListCollector ๊ตฌํํ๊ธฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import static java.util.stream.Collector.Characteristics.*;
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) -> { // ๋ ๋ฒ์งธ ์ฝํ
์ธ ์ ํฉ์ณ์ ์ฒซ ๋ฒ์งธ ๋์ ์๋ฅผ ๊ณ ์น๋ค.
list1.addAll(list2); // ๋ณ๊ฒฝ๋ ์ฒซ ๋ฒ์งธ ๋์ ์๋ฅผ ๋ฐํํ๋ค.
return list1;
};
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(
IDENTITY_FINISH, CONCURRENT)); // ์ปฌ๋ ํฐ์ ํ๋๊ทธ๋ฅผ IDENTITY_FINISH, CONCURRENT๋ก ์ค์
}
}
์ ๊ตฌํ์ด Collectors.toList ๋ฉ์๋๊ฐ ๋ฐํํ๋ ๊ฒฐ๊ณผ์ ์์ ํ ๊ฐ์ ๊ฒ์ ์๋์ง๋ง ์ฌ์ํ ์ต์ ํ๋ฅผ ์ ์ธํ๋ฉด ๋์ฒด๋ก ๋น์ทํ๋ค. ํนํ ์๋ฐ API์์ ์ ๊ณตํ๋ ์ปฌ๋ ํฐ๋ ์ฑ๊ธํค Collections.emptyList()๋ก ๋น ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค.
์ด์ ์๋ฐ์์ ์ ๊ณตํ๋ API ๋์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ปฌ๋ ํฐ๋ฅผ ๋ฉ๋ด ์คํธ๋ฆผ์ ๋ชจ๋ ์๋ฆฌ๋ฅผ ์์งํ๋ ์์ ์์ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
1
List<Dish> dishes = menu.stream.collect(new ToListCollector<Dish>());
๋ค์์ ๊ธฐ์กด์ ์ฝ๋๋ค.
1
List<Dish> dishes = menuStream.collect(toList());
๊ธฐ์กด ์ฝ๋์ toList๋ ํฉํ ๋ฆฌ์ง๋ง ์ฐ๋ฆฌ ToListCollector๋ new๋ก ์ธ์คํด์คํํ๋ค๋ ์ ์ด ๋ค๋ฅด๋ค.
์ปฌ๋ ํฐ ๊ตฌํ์ ๋ง๋ค์ง ์๊ณ ๋ ์ปค์คํ ์์ง ์ํํ๊ธฐ
IDENTITY_FINISH ์์ง ์ฐ์ฐ์์๋ Collector ์ธํฐํ์ด์ค๋ฅผ ์์ ํ ์๋ก ๊ตฌํํ์ง ์๊ณ ๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค. Stream์ ์ธ ํจ์ (๋ฐํ, ๋์ , ํฉ์นจ)์ ์ธ์๋ก ๋ฐ๋๋ค.