item 46 dodo4513 - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

์•„์ดํ…œ46 ์ŠคํŠธ๋ฆผ์—์„œ๋Š” ๋ถ€์ž‘์šฉ ์—†๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

  • ์ŠคํŠธ๋ฆผ์€ ์ฒ˜์Œ ๋ด์„œ๋Š” ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ๋‹ค. ์›ํ•˜๋Š” ์ž‘์—…์„ ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์กฐ์ฐจ ์–ด๋ ค์šธ์ง€ ๋ชจ๋ฅธ๋‹ค. ์„ฑ๊ณตํ•˜์—ฌ ํ”„๋กœ๊ทธ๋žจ์ด ๋™์ž‘ํ•˜๋”๋ผ๋„ ์žฅ์ ์ด ๋ฌด์—‡์ธ์ง€ ์‰ฝ๊ฒŒ ์™€ ๋‹ฟ์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค. ์ŠคํŠธ๋ฆผ์€ ๊ทธ์ € ๋˜ ํ•˜๋‚˜์˜ API๊ฐ€ ์•„๋‹Œ, ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์— ๊ธฐ์ดˆํ•œ ํŒจ๋Ÿฌ๋‹ค์ž„์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ŠคํŠธ๋ฆผ์ด ์ œ๊ณตํ•˜๋Š” ํ‘œํ˜„๋ ฅ, ์†๋„, (์ƒํ™ฉ์— ๋”ฐ๋ผ์„œ๋Š”) ๋ณ‘๋ ฌ์„ฑ์„ ์–ป์œผ๋ ค๋ฉด API๋Š” ๋งํ•  ๊ฒƒ ๋„ ์—†๊ณ  ์ด ํŒจ๋Ÿฌ๋‹ค์ž„๊นŒ์ง€ ํ•จ๊ป˜ ๋ฐ›์•„๋“ค์—ฌ์•ผ ํ•œ๋‹ค.
// ์ŠคํŠธ๋ฆผ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ•œ ์ฑ„ API๋งŒ ์‚ฌ์šฉํ–ˆ๋‹ค - ๋”ฐ๋ผ ํ•˜์ง€ ๋ง ๊ฒƒ!
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    });
}
  • forEach๊ฐ€ ๊ทธ์ € ์ŠคํŠธ๋ฆผ์ด ์ˆ˜ํ–‰ํ•œ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ผ ์ด์ƒ์„ ํ•˜๋Š” ๊ฒƒ(์ด ์˜ˆ์—์„œ๋Š” ๋žŒ๋‹ค๊ฐ€ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•จ)์„ ๋ณด๋‹ˆ ๋‚˜์œ ์ฝ”๋“œ์ผ ๊ฒƒ ๊ฐ™์€ ๋ƒ„์ƒˆ๊ฐ€ ๋‚œ๋‹ค.
// ์ŠคํŠธ๋ฆผ์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•ด ๋นˆ๋„ํ‘œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
    freq = words
        .collect(groupingBy(String::toLowerCase, counting()));
}
  • forEach ์—ฐ์‚ฐ์€ ์ข…๋‹จ ์—ฐ์‚ฐ ์ค‘ ๊ธฐ๋Šฅ์ด ๊ฐ€์žฅ ์ ๊ณ  ๊ฐ€์žฅ โ€˜๋œโ€™ ์ŠคํŠธ๋ฆผ๋‹ต๋‹ค. ๋Œ€๋†“๊ณ  ๋ฐ˜๋ณต์ ์ด๋ผ์„œ ๋ณ‘๋ ฌํ™”ํ•  ์ˆ˜๋„ ์—†๋‹ค. forEach ์—ฐ์‚ฐ์€ ์ŠคํŠธ๋ฆผ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ๋Š” ์“ฐ์ง€ ๋ง์ž. ๋ฌผ๋ก  ๊ฐ€๋”์€ ์ŠคํŠธ๋ฆผ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ์กด ์ปฌ๋ ‰์…˜์— ์ถ”๊ฐ€ํ•˜๋Š” ๋“ฑ์˜ ๋‹ค๋ฅธ ์šฉ๋„๋กœ๋„ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

java.util.stream.Collectors

  • ์ด ์ฝ”๋“œ๋Š” ์ˆ˜์ง‘๊ธฐ(collector)๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๊ผญ ๋ฐฐ์›Œ์•ผ ํ•˜๋Š” ์ƒˆ๋กœ์šด ๊ฐœ๋…์ด๋‹ค. java.util.stream.Collectors ํด๋ž˜์Šค๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ฌด๋ ค 39๊ฐœ๋‚˜ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ๊ทธ์ค‘์—๋Š” ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ 5๊ฐœ๋‚˜ ๋˜๋Š” ๊ฒƒ๋„ ์žˆ๋‹ค.

  • ์ฒ˜์Œ์—๋Š” ์‰ฝ๊ฒŒ ์ถ•์†Œ(reduction) ์ „๋žต์„ ์บก์Šํ™”ํ•œ ๋ธ”๋ž™๋ฐ•์Šค ๊ฐ์ฒด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค. ์—ฌ๊ธฐ์„œ ์ถ•์†Œ๋Š” ์ŠคํŠธ๋ฆผ์˜ ์›์†Œ๋“ค์„ ๊ฐ์ฒด ํ•˜๋‚˜์— ์ทจํ•ฉํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค. ์ˆ˜์ง‘๊ธฐ๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ๊ฐ์ฒด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ปฌ๋ ‰์…˜์ด๋ฉฐ, ๊ทธ๋ž˜์„œ โ€œcollectorโ€๋ผ๋Š” ์ด๋ฆ„์„ ์“ด๋‹ค.

  • ์ˆ˜์ง‘๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์˜ ์›์†Œ๋ฅผ ์†์‰ฝ๊ฒŒ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋ชจ์„ ์ˆ˜ ์žˆ๋‹ค. ์ˆ˜์ง‘๊ธฐ๋Š” ์ด ์„ธ ๊ฐ€์ง€๋กœ, toList(), toSet(), toCollection(collectionFactory)๋‹ค.

๋‹ค์–‘ํ•œ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์ž

1. ๋นˆ๋„ํ‘œ์—์„œ ๊ฐ€์žฅ ํ”ํ•œ ๋‹จ์–ด 10๊ฐœ๋ฅผ ๋ฝ‘์•„๋‚ด๋Š” ํŒŒ์ดํ”„๋ผ์ธ

List<String> topTen = freq.keySet().stream()
                                  .sorted(comparing(freq::get).reversed())
                                  .limit(10)
                                  .collect(toList());
  • comparing ๋ฉ”์„œ๋“œ๋Š” ํ‚ค ์ถ”์ถœ ํ•จ์ˆ˜๋ฅผ ๋ฐ›๋Š” ๋น„๊ต์ž ์ƒ์„ฑ ๋ฉ”์„œ๋“œ(์•„์ดํ…œ 14)๋‹ค.
  • ํ•œ์ •์  ๋ฉ”์„œ๋“œ ์ฐธ์กฐ์ด์ž, ์—ฌ๊ธฐ์„œ ํ‚ค ์ถ”์ถœ ํ•จ์ˆ˜๋กœ ์“ฐ์ธ freq::get์€ ์ž…๋ ฅ๋ฐ›์€ ๋‹จ์–ด(ํ‚ค)๋ฅผ ๋นˆ๋„ํ‘œ์—์„œ ์ฐพ์•„(์ถ”์ถœ) ๊ทธ ๋นˆ๋„๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ๊ฐ€์žฅ ํ”ํ•œ ๋‹จ์–ด๊ฐ€ ์œ„๋กœ ์˜ค๋„๋ก ๋น„๊ต์ž(comparing)๋ฅผ ์—ญ์ˆœ(reversed)์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค(sorted).
  • ๋งˆ์ง€๋ง‰ toList๋Š” Collectors์˜ ๋ฉ”์„œ๋“œ๋‹ค. ์ด์ฒ˜๋Ÿผ Collectors์˜ ๋ฉค๋ฒ„๋ฅผ ์ •์  ์ž„ํฌํŠธํ•˜์—ฌ ์“ฐ๋ฉด ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์ ธ, ํ”ํžˆ๋“ค ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•œ๋‹ค.

2. toMap ์ˆ˜์ง‘๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด์„ ์—ด๊ฑฐ ํƒ€์ž… ์ƒ์ˆ˜์— ๋งคํ•‘

private static final Map<String, Operation> stringToEnum =
        Stream.of(values()).collect(toMap(Object::toString, e -> e));
  • ์ด ๊ฐ„๋‹จํ•œ toMap ํ˜•ํƒœ๋Š” ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์›์†Œ๊ฐ€ ๊ณ ์œ ํ•œ ํ‚ค์— ๋งคํ•‘๋˜์–ด ์žˆ์„ ๋•Œ์ ํ•ฉํ•˜๋‹ค. ์ŠคํŠธ๋ฆผ ์›์†Œ ๋‹ค์ˆ˜๊ฐ€ ๊ฐ™์€ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํŒŒ์ดํ”„๋ผ์ธ์ด IllegalStateException์„ ๋˜์ง€๋ฉฐ ์ข…๋ฃŒ๋  ๊ฒƒ์ด๋‹ค.
  • ๋” ๋ณต์žกํ•œ ํ˜•ํƒœ์˜ toMap์ด๋‚˜ groupingBy๋Š” ์ด๋Ÿฐ ์ถฉ๋Œ์„ ๋‹ค๋ฃจ๋Š” ๋‹ค์–‘ํ•œ ์ „๋žต์„ ์ œ๊ณตํ•œ๋‹ค.

3. ๊ฐ ํ‚ค์™€ ํ•ด๋‹น ํ‚ค์˜ ํŠน์ • ์›์†Œ๋ฅผ ์—ฐ๊ด€ ์ง“๋Š” ๋งต์„ ์ƒ์„ฑํ•˜๋Š” ์ˆ˜์ง‘๊ธฐ

Map<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));
  • ์ธ์ˆ˜ 3๊ฐœ๋ฅผ ๋ฐ›๋Š” toMap์€ ์–ด๋–ค ํ‚ค์™€ ๊ทธ ํ‚ค์— ์—ฐ๊ด€๋œ ์›์†Œ๋“ค ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณจ๋ผ์—ฐ๊ด€ ์ง“๋Š” ๋งต์„ ๋งŒ๋“ค ๋•Œ ์œ ์šฉํ•˜๋‹ค.
  • ์—ฌ๊ธฐ์„œ ๋น„๊ต์ž๋กœ๋Š” BinaryOperator์—์„œ ์ •์  ์ž„ํฌํŠธํ•œ maxBy๋ผ๋Š” ์ •์  ํŒฉํ„ฐ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. maxBy๋Š” Comparator๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ BinaryOperator๋ฅผ ๋Œ๋ ค์ค€๋‹ค. ์ด ๊ฒฝ์šฐ ๋น„๊ต์ž ์ƒ์„ฑ ๋ฉ”์„œ๋“œ์ธ comparing์ด maxBy์— ๋„˜๊ฒจ์ค„ ๋น„๊ต์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์ž์‹ ์˜ ํ‚ค ์ถ”์ถœ ํ•จ์ˆ˜๋กœ๋Š” Album::sales๋ฅผ ๋ฐ›์•˜๋‹ค.
toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)
  • ์ธ์ˆ˜๊ฐ€ 3๊ฐœ์ธ toMap์€ ์ถฉ๋Œ์ด ๋‚˜๋ฉด ๋งˆ์ง€๋ง‰ ๊ฐ’์„ ์ทจํ•˜๋Š”(last-write-wins) ์ˆ˜์ง‘๊ธฐ๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ์œ ์šฉํ•˜๋‹ค. ๋งŽ์€ ์ŠคํŠธ๋ฆผ์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋น„๊ฒฐ์ •์ ์ด๋‹ค. ํ•˜์ง€๋งŒ ๋งคํ•‘ ํ•จ์ˆ˜๊ฐ€ ํ‚ค ํ•˜๋‚˜์— ์—ฐ๊ฒฐํ•ด์ค€ ๊ฐ’๋“ค์ด ๋ชจ๋‘ ๊ฐ™์„ ๋•Œ, ํ˜น์€ ๊ฐ’์ด ๋‹ค๋ฅด๋”๋ผ๋„ ๋ชจ๋‘ ํ—ˆ์šฉ๋˜๋Š” ๊ฐ’์ผ ๋•Œ ์ด๋ ‡๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ˆ˜์ง‘๊ธฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

4. ์•ŒํŒŒ๋ฒณํ™”ํ•œ ๋‹จ์–ด๋ฅผ ์•ŒํŒŒ๋ฒณํ™” ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์€ ๋‹จ์–ด๋“ค์˜ ๋ฆฌ์ŠคํŠธ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋งต์„ ์ƒ์„ฑ

// alphabetize ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฃจํ•‘
Map<String, List<String> req = words.collect(groupingBy(word -> alphabetize(word)))

// map value์˜ list๋ฅผ ์ถ•์†Œ
Map<String, Long> freq = words.collect(groupingBy(String::toLowerCase, counting()));

// ๋‹ค๋ฅธ map ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
Map<String, Long> freq = words.collect(groupingBy(String::toLowerCase, TreeMap::new, counting()));
  • groupingBy๋Š” ๋ถ„๋ฅ˜ ํ•จ์ˆ˜(classifier)๋ฅผ ๋ฐ›๊ณ  ์ถœ๋ ฅ์œผ๋กœ๋Š” ์›์†Œ๋“ค์„ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๋ชจ์•„ ๋†“์€ ๋งต์„ ๋‹ด์€ ์ˆ˜์ง‘๊ธฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

5. ๊ทธ ์™ธ

  1. ๋งŽ์ด ์“ฐ์ด์ง„ ์•Š์ง€๋งŒ groupingBy์˜ ์‚ฌ์ดŒ๊ฒฉ์ธ partitioningBy๋„ ์žˆ๋‹ค. ๋ถ„๋ฅ˜ ํ•จ์ˆ˜ ์ž๋ฆฌ์— ํ”„๋ ˆ๋””ํ‚คํŠธ(predicate)๋ฅผ ๋ฐ›๊ณ  ํ‚ค๊ฐ€ Boolean์ธ ๋งต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  2. Stream์˜ count ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ collect (counting()) ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•  ์ผ์€ ์ „ํ˜€ ์—†๋‹ค. Collections์—๋Š” ์ด๋Ÿฐ ์†์„ฑ์˜ ๋ฉ” ์„œ๋“œ๊ฐ€ 16๊ฐœ๋‚˜ ๋” ์žˆ๋‹ค. ๊ทธ์ค‘ 9๊ฐœ๋Š” ์ด๋ฆ„์ด summing, averaging, summarizing ์œผ๋กœ ์‹œ์ž‘ํ•˜๋ฉฐ ๊ฐ๊ฐ int, long, double ์ŠคํŠธ๋ฆผ์šฉ์œผ๋กœ ํ•˜๋‚˜์”ฉ ์กด์žฌํ•œ๋‹ค.

  3. minBy์™€ maxBy๋Š” ์ธ์ˆ˜๋กœ ๋ฐ›์€ ๋น„๊ต์ž๋ฅผ ์ด์šฉํ•ด ์ŠคํŠธ๋ฆผ์—์„œ ๊ฐ’์ด ๊ฐ€์žฅ ์ž‘์€ ํ˜น์€ ๊ฐ€์žฅ ํฐ ์›์†Œ๋ฅผ ์ฐพ์•„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. Stream ์ธํ„ฐํŽ˜์ด์Šค์˜ min๊ณผ max ๋ฉ”์„œ๋“œ๋ฅผ ์‚ด์ง ์ผ๋ฐ˜ํ™”ํ•œ ๊ฒƒ์ด๋‹ค.

  4. joining์€ (๋ฌธ์ž์—ด ๋“ฑ์˜) CharSequence ์ธ์Šคํ„ด์Šค์˜ ์ŠคํŠธ๋ฆผ์—๋งŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” joining์€ ๋‹จ์ˆœํžˆ ์›์†Œ๋“ค์„ ์—ฐ๊ฒฐ(concatenate)ํ•˜๋Š” ์ˆ˜์ง‘๊ธฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

ํ•ต์‹ฌ์ •๋ฆฌ

์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ์€ ๋ถ€์ž‘์šฉ ์—†๋Š” ํ•จ์ˆ˜ ๊ฐ์ฒด์— ์žˆ๋‹ค.
์ŠคํŠธ๋ฆผ๋ฟ ์•„๋‹ˆ๋ผ ์ŠคํŠธ๋ฆผ ๊ด€๋ จ ๊ฐ์ฒด์— ๊ฑด๋„ค์ง€๋Š” ๋ชจ๋“  ํ•จ์ˆ˜ ๊ฐ์ฒด๊ฐ€ ๋ถ€์ž‘์šฉ์ด ์—†์–ด์•ผ ํ•œ๋‹ค.
์ข…๋‹จ ์—ฐ์‚ฐ ์ค‘ forEach๋Š” ์ŠคํŠธ๋ฆผ์ด ์ˆ˜ํ–‰ํ•œ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ•  ๋•Œ๋งŒ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค.
๊ณ„์‚ฐ ์ž์ฒด์—๋Š” ์ด์šฉํ•˜์ง€ ๋ง์ž.
์ŠคํŠธ๋ฆผ์„ ์˜ฌ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ˆ˜์ง‘๊ธฐ๋ฅผ ์ž˜ ์•Œ์•„๋‘ฌ์•ผ ํ•œ๋‹ค.
๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ˆ˜์ง‘๊ธฐ ํŒฉํ„ฐ๋ฆฌ๋Š” toList, toSet, toMap, groupingBy, joining์ด๋‹ค.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ