Post

๐Ÿน chap5. ์ŠคํŠธ๋ฆผ ํ™œ์šฉ

5.1 ํ•„ํ„ฐ๋ง

์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋‚ด์šฉ์ด๋‹ค.

5.1.1 ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋กœ ํ•„ํ„ฐ๋ง

filter ๋ฉ”์„œ๋“œ๋Š” ํ”„๋ ˆ๋””์ผ€์ดํŠธ(boolean์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜)๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„์„œ ํ”„๋ ˆ๋””์ผ€์ดํŠธ์™€ ์ผ์น˜ํ•˜๋Š” ๋ชจ๋“  ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๋Š” ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

1
2
3
List<Dish> vegetarianMenu = menu.stream()
                                .filter(Dish::isVegetarian) // ์ฑ„์‹ ์š”๋ฆฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ฐธ์กฐ
                                .collect(toList());

5.1.2 ๊ณ ์œ  ์š”์†Œ ํ•„ํ„ฐ๋ง

์ŠคํŠธ๋ฆผ์€ ๊ณ ์œ  ์š”์†Œ๋กœ ์ด๋ฃจ์–ด์ง„ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” distinct ๋ฉ”์„œ๋“œ๋„ ์ง€์›ํ•œ๋‹ค(๊ณ ์œ  ์—ฌ๋ถ€๋Š” ์ŠคํŠธ๋ฆผ์—์„œ ๋งŒ๋“  ๊ฐ์ฒด์˜ hashCode, equals๋กœ ๊ฒฐ์ •๋œ๋‹ค).

1
2
3
4
5
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
       .filter(i -> i % 2 == 0)
       .distinct()
       .forEach(System.out::println);

5.2 ์ŠคํŠธ๋ฆผ ์Šฌ๋ผ์ด์‹ฑ

์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์Šคํ‚ตํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

5.2.1 ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ด์šฉํ•œ ์Šฌ๋ผ์ด์‹ฑ

์ž๋ฐ” 9๋Š” ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก takeWhile, dropWhile ๋‘ ๊ฐ€์ง€ ์ƒˆ๋กœ์šด ๋งค์„œ๋“œ๋ฅด ์ง€์›ํ•œ๋‹ค.

takeWhile ํ™œ์šฉ

์š”๋ฆฌ ๋ชฉ๋ก์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1
2
3
4
5
6
List<Dish> specialMenu = Arrays.asList(
    new Dish("seasonal fruit", true, 120, Dish.Type.OTHER),
    new Dish("prawns" false, 300, Dish.Type.FISH)
    new Dish("rice", true, 350, Dish.Type.OTHER),
    new Dish("chicken", false, 400, Dish.Type.MEAT),
    new Dish("french fries", true, 530, Dish.Type.OTHER));

320 ์นผ๋กœ๋ฆฌ ์ดํ•˜์˜ ์š”๋ฆฌ๋ฅผ ์„ ํƒํ•˜๊ธฐ ์œ„ํ•ด์„œ ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ? ์ด์ „ filter์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
List<Dish> filteredMenu
    = specialMenu.stream()
                 .filter(dish -> dish.getCalories() < 320)
                 .collect(toList());

์š”๋ฆฌ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ด๋ฏธ ์นผ๋กœ๋ฆฌ ์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์— ์ฃผ๋ชฉํ•˜์ž. filter ์—ฐ์‚ฐ์„ ์ด์šฉํ•˜๋ฉด ์ „์ฒด ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ๊ฐ ์š”์†Œ์— ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

๋”ฐ๋ผ์„œ ์ด๋ฏธ ์ •๋ ฌ๋˜์–ด ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ด์šฉํ•ด 320 ์นผ๋กœ๋ฆฌ๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€ ์š”๋ฆฌ๊ฐ€ ๋‚˜์™”์„ ๋•Œ ๋ฐ˜๋ณต ์ž‘์—…์„ ์ค‘๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ํฐ ์ŠคํŠธ๋ฆผ์—์„œ๋Š” ์ƒ๋‹นํ•œ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

takeWhile ์—ฐ์‚ฐ์„ ์ด์šฉํ•˜๋ฉด ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์„ ํฌํ•จํ•œ ๋ชจ๋“  ์ŠคํŠธ๋ฆผ์— ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ ์šฉํ•ด ์ŠคํŠธ๋ฆผ์„ ์Šฌ๋ผ์ด์Šคํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
List<Dish> slicedMenu1
    = specialMenu.stream()
                 .takeWhile(dish -> dish.getCalories() < 320)
                 .collect(toList());

dropWhile ํ™œ์šฉ

320 ์นผ๋กœ๋ฆฌ๋ณด๋‹ค ํฐ ์š”์†Œ(๋‚˜๋จธ์ง€ ์š”์†Œ)๋ฅผ ํƒ์ƒ‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” dropWhile์„ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

1
2
3
4
List<Dish> slicedMenu2
    = specialMenu.stream()
                 .dropWhile(dish -> dish.getCalories() < 320)
                 .collect(toList());

dropWhile์€ takeWhile๊ณผ ์ •๋ฐ˜๋Œ€์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. dropWhile์€ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๊ฐ€ ์ฒ˜์Œ์œผ๋กœ ๊ฑฐ์ง“์ด ๋˜๋Š” ์ง€์ ๊นŒ์ง€ ๋ฐœ๊ฒฌ๋œ ์š”์†Œ๋ฅผ ๋ฒ„๋ฆฐ๋‹ค. ํ”„๋ ˆ๋””์ผ€์ดํŠธ๊ฐ€ ๊ฑฐ์ง“์ด ๋˜๋ฉด ๊ทธ ์ง€์ ์—์„œ ์ž‘์—…์„ ์ค‘๋‹จํ•˜๊ณ  ๋‚จ์€ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. dropWhile์€ ๋ฌดํ•œํ•œ ๋‚จ์€ ์š”์†Œ๋ฅผ ๊ฐ€์ง„ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์—์„œ๋„ ๋™์ž‘ํ•œ๋‹ค.

5.2.2 ์ŠคํŠธ๋ฆผ ์ถ•์†Œ

limit

์ŠคํŠธ๋ฆผ์€ ์ฃผ์–ด์ง„ ๊ฐ’ ์ดํ•˜์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ–๋Š” ์ƒˆ๋กœ์šด ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” limit(n) ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค.

1
2
3
4
List<Dish> dishes = specialMenu.stream()
                               .filter(dish -> dish.getCalories() > 300)
                               .limit(3)
                               .collect(toList());

ํ”„๋ ˆ๋””์ผ€์ดํŠธ์™€ ์ผ์น˜ํ•˜๋Š” ์ฒ˜์Œ ์„ธ ์š”์†Œ๋ฅผ ์„ ํƒํ•œ ๋‹ค์Œ์— ์ฆ‰์‹œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

โ†’ ๋ชจ๋“  ์š”์†Œ๋ฅผ ํ•„ํ„ฐ๋ง ํ•˜์ง€ ์•Š๋Š”๋‹ค!

์ •๋ ฌ๋˜์ง€ ์•Š์€ ์ŠคํŠธ๋ฆผ์—๋„ limit์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์†Œ์Šค๊ฐ€ ์ •๋ ฌ๋˜์–ด ์žˆ์ง€ ์•Š์•˜๋‹ค๋ฉด limit์˜ ๊ฒฐ๊ณผ๋„ ์ •๋ ฌ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.

5.2.3 ์š”์†Œ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

skip

์ŠคํŠธ๋ฆผ์€ ์ฒ˜์Œ n๊ฐœ ์š”์†Œ๋ฅผ ์ œ์™ธํ•œ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” skip(n) ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค. n๊ฐœ ์ดํ•˜์˜ ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๋Š” skip(n)์„ ํ˜ธ์ถœํ•˜๋ฉด ๋นˆ ์ŠคํŠธ๋ฆผ์ด ๋ฐ˜ํ™˜๋œ๋‹ค.

limit(n)๊ณผ skip(n)์€ ์ƒํ˜ธ ๋ณด์™„์ ์ธ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

1
2
3
4
5
// ์ฒ˜์Œ ๋‘ ์š”๋ฆฌ๋Š” ๊ฑด๋„ˆ๋›ด ๋‹ค์Œ 300์นผ๋กœ๋ฆฌ ๋„˜๋Š” ์š”๋ฆฌ ๋ฐ˜ํ™˜
List<Dish> dishes = menu.stream()
                        .filter(d -> d.getCalories() > 300)
                        .skip(2)
                        .collect(toList());

  • Quiz. ํ•„ํ„ฐ๋ง

    ์ŠคํŠธ๋ฆผ์„ ์ด์šฉํ•ด์„œ ์ฒ˜์Œ ๋“ฑ์žฅํ•˜๋Š” ๋‘ ๊ณ ๊ธฐ ์š”๋ฆฌ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์‹œ์˜ค.

    • ์ •๋‹ต

      1
      2
      3
      4
      
        List<Dish> meatDishes = menu.stream()
                                    .filter(d -> d.getType() == Dish.Type.MEAT)
                                    .limit(2)
                                    .collect(toList());
      

5.3 ๋งคํ•‘

์ŠคํŠธ๋ฆผ์˜ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

ex. ํŠน์ • ๊ฐ์ฒด์—์„œ ํŠน์ • ๋ฐ์ดํ„ฐ ์„ ํƒ

5.3.1 ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”์†Œ์— ํ•จ์ˆ˜ ์ ์šฉํ•˜๊ธฐ

map

์ŠคํŠธ๋ฆผ์€ ํ•จ์ˆ˜๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” map ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค. ์ธ์ˆ˜๋กœ ์ œ๊ณต๋œ ํ•จ์ˆ˜๋Š” ๊ฐ ์š”์†Œ์— ์ ์šฉ๋˜๋ฉฐ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์ƒˆ๋กœ์šด ์š”์†Œ๋กœ ๋งคํ•‘๋œ๋‹ค. (์ด ๊ณผ์ •์€ โ€˜๊ธฐ์กด์˜ ๊ฐ’์„ ๊ณ ์นœ๋‹คโ€™๋ผ๋Š” ๊ฐœ๋…๋ณด๋‹ค โ€˜์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ๋งŒ๋“ ๋‹คโ€™๋ผ๋Š” ๊ฐœ๋…์— ๊ฐ€๊นŒ์šฐ๋ฏ€๋กœ ๋ณ€ํ™˜์— ๊ฐ€๊นŒ์šด ๋งคํ•‘์ด๋ผ๋Š” ๋‹จ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.)

1
2
3
4
// Dish::getName์„ map ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌํ•ด ์ŠคํŠธ๋ฆผ์˜ ์š”๋ฆฌ๋ช… ์ถ”์ถœ
List<String> dishNames = menu.stream()
                             .map(Dish::getName)
                             .collect(toList());

getName์€ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ map ๋ฉ”์„œ๋“œ์˜ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์€ Stream ํ˜•์‹์„ ๊ฐ–๋Š”๋‹ค.

์ด๋ฒˆ์—” ๋‹ค๋ฅธ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

1
2
3
4
5
// String::length๋ฅผ map ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌํ•ด ์ŠคํŠธ๋ฆผ์˜ ๊ธธ์ด๋ฅผ ์ถ”์ถœ
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
                                 .map(String::length)
                                 .collect(toList());

์œ„ ๋‘ ์˜ˆ์‹œ๋ฅผ ํ•ฉ์ณ๋ณด๋ฉด

1
2
3
4
5
// ์š”๋ฆฌ๋ช…์˜ ๊ธธ์ด ์ถ”์ถœ
List<Integer> dishNameLengths = menu.stream()
                                    .map(Dish::getName)
                                    .map(String::length)
                                    .collect(toList());

์ด์ฒ˜๋Ÿผ map ๋ฉ”์„œ๋“œ๋ฅผ ์—ฐ๊ฒฐํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

5.3.2 ์ŠคํŠธ๋ฆผ์˜ ํ‰๋ฉดํ™”

๋ฆฌ์ŠคํŠธ์—์„œ ๊ณ ์œ  ๋ฌธ์ž๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ ค ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด [โ€Helloโ€, โ€œWorldโ€] ๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฒฐ๊ณผ๋กœ [โ€Hโ€, โ€œeโ€, โ€œlโ€, โ€œoโ€, โ€œWโ€, โ€œrโ€, โ€œdโ€]๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค.

๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ๊ฐ ๋‹จ์–ด๋ฅผ ๋ฌธ์ž๋กœ ๋งคํ•‘ํ•œ ๋‹ค์Œ distinct๋กœ ์ค‘๋ณต๋œ ๋ฌธ์ž๋ฅผ ํ•„ํ„ฐ๋งํ•ด ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•  ๊ฒƒ์ด๋‹ค.

1
2
3
4
words.stream()
     .map(word -> word.split(""))
     .distinct()
     .collect(toList());

์œ„ ์ฝ”๋“œ์—์„œ map์œผ๋กœ ์ „๋‹ฌํ•œ ๋žŒ๋‹ค๋Š” ๊ฐ ๋‹จ์–ด์˜ String[] (๋ฌธ์ž์—ด ๋ฐฐ์—ด)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ์ ์ด ๋ฌธ์ œ๋‹ค. ๋”ฐ๋ผ์„œ map ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ์ŠคํŠธ๋ฆผ์˜ ํ˜•์‹์€ Stream<String[]>์ด๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์€ ๋ฌธ์ž์—ด์˜ ์ŠคํŠธ๋ฆผ์„ ํ‘œํ˜„ํ•  Stream์ด๋‹ค.

์œ„ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ flatMap์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

map๊ณผ Arrays.stream ํ™œ์šฉ

์šฐ์„  ๋ฐฐ์—ด ์ŠคํŠธ๋ฆผ ๋Œ€์‹  ๋ฌธ์ž์—ด ์ŠคํŠธ๋ฆผ์ด ํ•„์š”ํ•˜๋‹ค. ๋ฌธ์ž์—ด์„ ๋ฐ›์•„ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“œ๋Š” Arrays.stream() ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋‹ค.

1
2
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

์œ„ ์˜ˆ์ œ์˜ ํŒŒ์ดํ”„๋ผ์ธ์— Arrays.stream() ๋ฉ”์„œ๋“œ๋ฅผ ์ ์šฉํ•ด๋ณด์ž.

1
2
3
4
5
words.stream()
     .map(word -> word.split("")) // ๊ฐ ๋‹จ์–ด๋ฅผ ๊ฐœ๋ณ„ ๋ฌธ์ž์—ด ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
     .map(Arrays::stream) // ๊ฐ ๋ฐฐ์—ด์„ ๋ณ„๋„์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ƒ์„ฑ
     .distinct()
     .collect(toList());

๊ฒฐ๊ตญ ์ŠคํŠธ๋ฆผ ๋ฆฌ์ŠคํŠธ(์—„๋ฐ€ํžˆ ๋”ฐ์ง€๋ฉด List<Stream>)๊ฐ€ ๋งŒ๋“ค์–ด์ง€๋ฉด์„œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์•˜๋‹ค. ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด **๋จผ์ € ๊ฐ ๋‹จ์–ด๋ฅผ ๊ฐœ๋ณ„ ๋ฌธ์ž์—ด๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฐฐ์—ด๋กœ ๋งŒ๋“  ๋‹ค์Œ์— ๊ฐ ๋ฐฐ์—ด์„ ๋ณ„๋„์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค.**

flatMap ์‚ฌ์šฉ

flatMap์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
List<String> uniqueCharacters = 
    words.stream()
         .map(word -> word.split("")) // ๊ฐ ๋‹จ์–ด๋ฅผ ๊ฐœ๋ณ„ ๋ฌธ์ž๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
         .flatMap(Arrays::stream) // ์ƒ์„ฑ๋œ ์ŠคํŠธ๋ฆผ์„ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ํ‰๋ฉดํ™”
         .distinct()
         .collect(toList());

flatMap์€ ๊ฐ ๋ฐฐ์—ด์„ ์ŠคํŠธ๋ฆผ์ด ์•„๋‹ˆ๋ผ ์ŠคํŠธ๋ฆผ์˜ ์ฝ˜ํ…์ธ ๋กœ ๋งคํ•‘ํ•œ๋‹ค. ์ฆ‰, map(Arrays::stream)๊ณผ ๋‹ฌ๋ฆฌ flatMap์€ ํ•˜๋‚˜์˜ ํ‰๋ฉดํ™”๋œ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

flatMap ๋ฉ”์„œ๋“œ๋Š” ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ๊ฐ’์„ ๋‹ค๋ฅธ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“  ๋‹ค์Œ์— ๋ชจ๋“  ์ŠคํŠธ๋ฆผ์„ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  • Quiz. ๋งคํ•‘
    1. ์ˆซ์ž ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ ๊ฐ ์ˆซ์ž์˜ ์ œ๊ณฑ๊ทผ์œผ๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์‹œ์˜ค. ์˜ˆ๋ฅผ ๋“ค์–ด [1, 2, 3, 4, 5]๊ฐ€ ์ฃผ์–ด์ง€๋ฉด [1, 4, 9, 16, 25]๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.
      • ์ •๋‹ต
      1
      2
      3
      4
      
       List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
       List<Integer> squares = numbers.stream()
                                      .map(n -> n * n)
                                      .collect(toList());
      
    2. ๋‘ ๊ฐœ์˜ ์ˆซ์ž ๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ์„ ๋•Œ ๋ชจ๋“  ์ˆซ์ž ์Œ์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์‹œ์˜ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‘ ๊ฐœ์˜ ๋ฆฌ์ŠคํŠธ [1, 2, 3]๊ณผ [3, 4]๊ฐ€ ์ฃผ์–ด์ง€๋ฉด [(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.
      • ์ •๋‹ต
      1
      2
      3
      4
      5
      6
      7
      8
      
       List<Integer> numbers1 = Arrays.asList(1, 2, 3);
       List<Integer> numbers2 = Arrays.asList(3, 4);
       List<int[]> pairs 
           = numbers1.stream()
                     .flatMap(i -> numbers2.stream()
                                           .map(j -> new int[]{i, j})
                     )
                     .collect(toList());
      

      ๋‘ ๊ฐœ์˜ map์„ ์ด์šฉํ•ด์„œ ๋‘ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณตํ•œ ๋‹ค์Œ์— ์ˆซ์ž ์Œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ๋กœ Stream<Stream<Integer[]ยป>๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฒฐ๊ณผ๋ฅผ Stream<Integer[]>๋กœ ํ‰๋ฉดํ™”ํ•œ ์ŠคํŠธ๋ฆผ์ด ํ•„์š”ํ•˜๋‹ค. ๋ฐ”๋กœ flatMap์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

    3. ์ด์ „ ์˜ˆ์ œ์—์„œ ํ•ฉ์ด 3์œผ๋กœ ๋‚˜๋ˆ„์–ด๋–จ์–ด์ง€๋Š” ์Œ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด (2, 4), (3, 3)์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.
      • ์ •๋‹ต

        1
        2
        3
        4
        5
        6
        7
        8
        9
        
          List<Integer> numbers1 = Arrays.asList(1, 2, 3);
          List<Integer> numbers2 = Arrays.asList(3, 4);
          List<int[]> pairs 
              = numbers1.stream()
                        .flatMap(i -> numbers2.stream()
                                              .filter(j -> (i + j) % 3 == 0)
                                              .map(j -> new int[]{i, j})
                        )
                        .collect(toList());
        

        filter๋ฅผ ํ”„๋ ˆ๋””์ผ€์ดํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋‹ค. flatMap์„ ์‹คํ–‰ํ•˜๋ฉด ์ˆซ์ž ์Œ์„ ํฌํ•จํ•˜๋Š” int[] ์ŠคํŠธ๋ฆผ์ด ๋ฐ˜ํ™˜๋˜๋ฏ€๋กœ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ์ˆซ์ž ์Œ์˜ ํ•ฉ์ด 3์œผ๋กœ ๋‚˜๋ˆ„์–ด๋–จ์–ด์ง€๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

5.4 ๊ฒ€์ƒ‰๊ณผ ๋งค์นญ

ํŠน์ • ์†์„ฑ์ด ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ์— ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

5.4.1 ํ”„๋ ˆ๋””์ผ€์ดํŠธ๊ฐ€ ์ ์–ด๋„ ํ•œ ์š”์†Œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ

anyMatch

ํ”„๋ ˆ๋””์ผ€์ดํŠธ๊ฐ€ ์ฃผ์–ด์ง„ ์ŠคํŠธ๋ฆผ์—์„œ ์ ์–ด๋„ ํ•œ ์š”์†Œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

1
2
3
4
// menu์— ์ฑ„์‹์š”๋ฆฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์˜ˆ์ œ
if(menu.stream().anyMatch(Dish::isVegetarian)) {
    System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch๋Š” boolean์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ตœ์ข… ์—ฐ์‚ฐ์ด๋‹ค.

5.4.2 ํ”„๋ ˆ๋””์ผ€์ดํŠธ๊ฐ€ ๋ชจ๋“  ์š”์†Œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌ

allMatch

anyMatch์™€ ๋‹ฌ๋ฆฌ ์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ์š”์†Œ๊ฐ€ ์ฃผ์–ด์ง„ ํ”„๋ ˆ๋””์ผ€์ดํŠธ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.

1
2
3
// ๊ฑด๊ฐ•์‹ ์ฐพ๊ธฐ(1000์นผ๋กœ๋ฆฌ ์ดํ•˜)
boolean isHealthy = menu.stream()
                        .allMatch(dish -> dish.getCalories() < 1000);

noneMatch

allMatch์™€ ๋ฐ˜๋Œ€ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ฆ‰, ์ฃผ์–ด์ง„ ํ”„๋ ˆ๋””์ผ€์ดํŠธ์™€ ์ผ์น˜ํ•˜๋Š” ์š”์†Œ๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

1
2
boolean isHealthy = menu.stream()
                        .noneMatch(d -> d.getCalories >= 1000);
anyMatch, allMatch, noneMatch๋Š” ์ŠคํŠธ๋ฆผ ์‡ผํŠธ์„œํ‚ท ๊ธฐ๋ฒ•, ์ฆ‰ ์ž๋ฐ”์˜ &&,ย ์™€ ๊ฐ™์€ ์—ฐ์‚ฐ์„ ํ™œ์šฉํ•œ๋‹ค.

(๋ชจ๋“  ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜)

5.4.3 ์š”์†Œ ๊ฒ€์ƒ‰

findAny

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

1
2
3
4
5
// ์ฑ„์‹ ์š”๋ฆฌ ์„ ํƒ
Optional<Dish> dish =
    menu.stream()
        .filter(Dish::isVegetarian)
        .findAny();

์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹จ์ผ ๊ณผ์ •์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ตœ์ ํ™”๋œ๋‹ค. ์ฆ‰, ์‡ผํŠธ์„œํ‚ท์„ ์ด์šฉํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ๋Š” ์ฆ‰์‹œ ์‹คํ–‰์„ ์ข…๋ฃŒํ•œ๋‹ค.

Optional์ด๋ž€?

Optional ํด๋ž˜์„œ(java.util.Optional>๋Š” ๊ฐ’์˜ ์กด์žฌ๋‚˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ํด๋ž˜์Šค๋‹ค. ์ด์ „ ์˜ˆ์ œ์—์„œ findAny๋Š” ์•„๋ฌด ์š”์†Œ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. null์€ ์‰ฝ๊ฒŒ ์—๋Ÿฌ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ž๋ฐ” 8์—์„  Optional๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

Optional์€ ๊ฐ’์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๊ฐ’์ด ์—†์„ ๋•Œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ๊ฐ•์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

  • isPresent()๋Š” Optional์ด ๊ฐ’์„ ํฌํ•จํ•˜๋ฉด ์ฐธ(true)์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๊ฐ’์„ ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฑฐ์ง“(false)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ifPresent(Consumer block)์€ ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ฃผ์–ด์ง„ ๋ธ”๋ก์„ ์‹คํ–‰ํ•œ๋‹ค. Consumer ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค์—๋Š” T ํ˜•์‹์˜ ์ธ์ˆ˜๋ฅผ ๋ฐ›์œผ๋ฉฐ void๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋žŒ๋‹ค๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • T get()์€ ๊ฐ’์ด ์กด์žฌํ•˜๋ฉด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๊ฐ’์ด ์—†์œผ๋ฉด NoSuchElementException์„ ์ผ์œผํ‚จ๋‹ค.
  • T orElse (T other)๋Š” ๊ฐ’์ด ์žˆ์œผ๋ฉด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๊ฐ’์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
1
2
3
4
menu.stream()
    .filter(Dish::isVegetarian)
    .findAny() // Optional<Dish> ๋ฐ˜ํ™˜
    .ifPresent(dish -> System.out.println(dish.getName()); // ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ถœ๋ ฅ, ์—†์œผ๋ฉด ์•„๋ฌด์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค. 

์œ„ ์ฝ”๋“œ์—์„œ findAny๋กœ Optional๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์— ์š”๋ฆฌ๋ช…์ด null์ธ์ง€ ๊ฒ€์‚ฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

5.4.4 ์ฒซ ๋ฒˆ์งธ ์š”์†Œ ์“ฐ๊ธฐ

๋ฆฌ์ŠคํŠธ ๋˜๋Š” ์ •๋ ฌ๋œ ์—ฐ์† ๋ฐ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ์ƒ์„ฑ๋œ ์ŠคํŠธ๋ฆผ์ฒ˜๋Ÿผ ์ผ๋ถ€ ์ŠคํŠธ๋ฆผ์—๋Š” ๋…ผ๋ฆฌ์ ์ธ ์•„์ดํ…œ ์ˆœ์„œ๊ฐ€ ์ •ํ•ด์ ธ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ์ŠคํŠธ๋ฆผ์—์„œ ์ฒซ ๋ฒˆ์งธ ์š”์†Œ๋ฅผ ์ฐพ์•„๋ณด์ž.

1
2
3
4
5
6
7
// 3์œผ๋กœ ๋–จ์–ด์ง€๋Š” ์ฒซ ๋ฒˆ์งธ ์ œ๊ณฑ๊ฐ’์„ ๋ฐ˜ํ™˜
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByTree = 
    someNumbers.stream()
               .map(n -> n * n)
               .filter(n -> n % 3 == 0)
               .findFirst();

5.5 ๋ฆฌ๋“€์‹ฑ(ํด๋“œ)

๋ชจ๋“  ์ŠคํŠธ๋ฆผ ์š”์†Œ๋ฅผ ์ฒ˜๋ฆฌํ•ด์„œ ๊ฐ’์œผ๋กœ ๋„์ถœํ•˜๋Š” ๊ฒƒ

5.5.1 ์š”์†Œ์˜ ํ•ฉ

1
2
3
4
5
// ๋ฆฌ์ŠคํŠธ์˜ ์ˆซ์ž ์š”์†Œ์˜ ํ•ฉ
int sum = 0;
for (int x : numbers) {
    sum += x;
}

numbers์˜ ๊ฐ ์š”์†Œ๋Š” ๊ฒฐ๊ณผ์— ๋ฐ˜๋ณต์ ์œผ๋กœ ๋”ํ•ด์ง„๋‹ค. ๋ฆฌ์ŠคํŠธ์—์„œ ํ•˜๋‚˜์˜ ์ˆซ์ž๊ฐ€ ๋‚จ์„ ๋•Œ๊นŒ์ง€ reduce ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•œ๋‹ค. ์ฝ”๋“œ์—๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ 2๊ฐœ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

  • sum ๋ณ€์ˆ˜์˜ ์ดˆ๊นƒ๊ฐ’ 0
  • ๋ฆฌ์ŠคํŠธ์ด ๋ชจ๋“  ์š”์†Œ๋ฅผ ์กฐํ•ฉํ•˜๋Š” ์—ฐ์‚ฐ(+)

reduce๋ฅผ ์ด์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ˜๋ณต๋œ ํŒจํ„ด์„ ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. reduce๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ์š”์†Œ๋ฅผ ๋”ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce๋Š” 2๊ฐœ์˜ ์ธ์ˆ˜๋ฅผ ๊ฐ–๋Š”๋‹ค.

  • ์ดˆ๊นƒ๊ฐ’ 0
  • ๋‘ ์š”์†Œ๋ฅผ ์กฐํ•ฉํ•ด์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋งŒ๋“œ๋Š” BinaryOperator. ์˜ˆ์ œ์—์„œ๋Š” ๋žŒ๋‹ค ํ‘œํ˜„์‹ (a, b) โ†’ (a + b)๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

์ŠคํŠธ๋ฆผ์ด ํ•˜๋‚˜์˜ ๊ฐ’์œผ๋กœ ์ค„์–ด๋“ค ๋•Œ๊นŒ์ง€ ๋žŒ๋‹ค๋Š” ๊ฐ ์š”์†Œ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์กฐํ•ฉํ•œ๋‹ค.

๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ์ด์šฉํ•ด์„œ ์ด ์ฝ”๋“œ๋ฅผ ์ข€ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์ž๋ฐ” 8์—์„œ๋Š” Integer ํด๋ž˜์Šค์—์„œ ๋‘ ์ˆซ์ž๋ฅผ ๋”ํ•˜๋Š” ์ •์  sum ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ง์ ‘ ๋žŒ๋‹ค ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

1
int sum = numbers.stream().reduce(0, Integer::sum);

์ดˆ๊นƒ๊ฐ’ ์—†์Œ

์ดˆ๊นƒ๊ฐ’์„ ๋ฐ›์ง€ ์•Š๋„๋ก ์˜ค๋ฒ„๋กœ๋“œ๋œ reduce๋„ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด reduce๋Š” Optional ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

1
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

์œ„์˜ ๊ฒฝ์šฐ ์ŠคํŠธ๋ฆผ์— ์•„๋ฌด ์š”์†Œ๋„ ์—†๋Š” ์ƒํ™ฉ์—์„œ ์ดˆ๊นƒ๊ฐ’์ด ์—†์œผ๋ฏ€๋กœ reduce๋Š” ํ•ฉ๊ณ„๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๋‹ค. ๋”ฐ๋ผ์„œ ํ•ฉ๊ณ„๊ฐ€ ์—†์Œ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก Optional ๊ฐ์ฒด๋กœ ๊ฐ์‹ผ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

5.5.2 ์ตœ๋Œ“๊ฐ’๊ณผ ์ตœ์†Ÿ๊ฐ’

์ตœ๋Œ“๊ฐ’๊ณผ ์ตœ์†Ÿ๊ฐ’์„ ์ฐพ์„ ๋•Œ๋„ reduce๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. reduce๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์—์„œ ์ตœ๋Œ“๊ฐ’๊ณผ ์ตœ์†Ÿ๊ฐ’์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์ž. reduce๋Š” ๋‘ ์ธ์ˆ˜๋ฅผ ๋ฐ›๋Š”๋‹ค.

  • ์ดˆ๊นƒ๊ฐ’
  • ์ŠคํŠธ๋ฆผ์˜ ๋‘ ์š”์†Œ๋ฅผ ํ•ฉ์ณ์„œ ํ•˜๋‚˜์˜ ๊ฐ’์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉํ•  ๋žŒ๋‹ค
1
2
// ์ตœ๋Œ“๊ฐ’
Optional<Integer> max = numbers.stream().reduce(Integer::max);
1
2
// ์ตœ์†Ÿ๊ฐ’
Optional<Integer> min = numbers.stream().reduce(Integer::min);

Integer::min ๋Œ€์‹  ๋žŒ๋‹ค ํ‘œํ˜„์‹ (x, y) โ†’ x < y ? x : y๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฌด๋ฐฉํ•˜์ง€๋งŒ ๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํ‘œํ˜„์ด ๋” ์ฝ๊ธฐ ์‰ฝ๋‹ค.

  • Quiz. ๋ฆฌ๋“€์Šค

    map๊ณผ reduce ๋งค์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์˜ ์š”๋ฆฌ ๊ฐœ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜์‹œ์˜ค.

    • ์ •๋‹ต

      ์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”์†Œ๋ฅผ 1๋กœ ๋งคํ•‘ํ•œ ๋‹ค์Œ์— reduce๋กœ ์ด๋“ค์˜ ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์ŠคํŠธ๋ฆผ์— ์ €์žฅ๋œ ์ˆซ์ž๋ฅผ ์ฐจ๋ก€๋กœ ํ•œ๋‹ค.

      1
      2
      3
      4
      
        int count = 
            menu.stream()
                .map(d -> 1)
                .reduce(0, (a, b) -> a + b);
      

      map๊ณผ reduce๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ธฐ๋ฒ•์„ ๋งต ๋ฆฌ๋“€์Šค ํŒจํ„ด์ด๋ผ ํ•˜๋ฉฐ, ์‰ฝ๊ฒŒ ๋ณ‘๋ ฌํ™”ํ•˜๋Š” ํŠน์ง• ๋•๋ถ„์— ๊ตฌ๊ธ€์ด ์›น ๊ฒ€์ƒ‰์— ์ ์šฉํ•˜๋ฉด์„œ ์œ ๋ช…ํ•ด์กŒ๋‹ค.

      1
      
        long count = menu.stream().count();
      

      ์ด๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

reduce ๋ฉ”์„œ๋“œ์˜ ์žฅ์ ๊ณผ ๋ณ‘๋ ฌํ™”

reduce๋ฅผ ์ด์šฉํ•˜๋ฉด ๋‚ด๋ถ€ ๋ฐ˜๋ณต์ด ์ถ”์ƒํ™”๋˜๋ฉด์„œ ๋‚ด๋ถ€ ๊ตฌํ˜„์—์„œ ๋ณ‘๋ ฌ๋กœ reduce๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ๋ฐ˜๋ณต์ ์ธ ํ•ฉ๊ณ„์—์„œ๋Š” sum ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์‰ฝ๊ฒŒ ๋ณ‘๋ ฌํ™”ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

์ฝ”๋“œ๋ฅผ ๋ณ‘๋ ฌ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” stream()์„ parallelStream()์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๋œ๋‹ค.

1
int sum = numbers.parallelStream().reduce(0, Integer::sum);

์œ„์ฒ˜๋Ÿผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์กฐ๊ฑด์ด ์žˆ๋‹ค. reduce์— ๋„˜๊ฒจ์ค€ ๋žŒ๋‹ค์˜ ์ƒํƒœ(์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜ ๊ฐ™์€)๊ฐ€ ๋ฐ”๋€Œ์ง€ ๋ง์•„์•ผ ํ•˜๋ฉฐ, ์—ฐ์‚ฐ์ด ์–ด๋–ค ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋”๋ผ๋„ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๊ตฌ์กฐ์—ฌ์•ผ ํ•œ๋‹ค.

์ŠคํŠธ๋ฆผ ์—ฐ์‚ฐ : ์ƒํƒœ ์—†์Œ๊ณผ ์ƒํƒœ ์žˆ์Œ

  • ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๊ฐ–์ง€ ์•Š๋Š” ์—ฐ์‚ฐ
    • map, filter ๋“ฑ
    • ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ์—์„œ ๊ฐ ์š”์†Œ๋ฅผ ๋ฐ›์•„ 0 ๋˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณด๋‚ธ๋‹ค.
  • ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” ์—ฐ์‚ฐ
    • reduce, sum, max ๋“ฑ
    • ๊ฒฐ๊ณผ๋ฅผ ๋ˆ„์ ํ•  ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํ•„์š” โ†’ ์ŠคํŠธ๋ฆผ์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ์š”์†Œ ์ˆ˜์™€ ๊ด€๊ณ„์—†์ด ๋‚ด๋ถ€ ์ƒํƒœ์˜ ํฌ๊ธฐ๋Š” ํ•œ์ •๋˜์–ด ์žˆ๋‹ค.
    • sorted, distinct ๋“ฑ์€ filter๋‚˜ map์ฒ˜๋Ÿผ ์ŠคํŠธ๋ฆผ์„ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๋Š”๋‹ค.
    • ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ๋ฅผ ์ •๋ ฌํ•˜๊ฑฐ๋‚˜ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด ๊ณผ๊ฑฐ์˜ ์ด๋ ฅ์„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

      (ex. ์–ด๋–ค์š”์†Œ๋ฅผ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ๋ชจ๋“  ์š”์†Œ๊ฐ€ ๋ฒ„ํผ์— ์ถ”๊ฐ€๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.)

5.6 ์‹ค์ „ ์—ฐ์Šต

ํŠธ๋žœ์žญ์…˜(๊ฑฐ๋ž˜)์„ ์‹คํ–‰ํ•˜๋Š” ๊ฑฐ๋ž˜์ž ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์ž. ์šฐ๋ฆฌ์˜ ๊ด€๋ฆฌ์ž๊ฐ€ ์—ฌ๋Ÿ ๊ฐ€์ง€ ์งˆ๋ฌธ์˜ ๋‹ต์„ ์ฐพ์œผ๋ผ๊ณ  ์š”์ฒญํ–ˆ๋‹ค.

  1. 2011๋…„์— ์ผ์–ด๋‚œ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜์„ ์ฐพ์•„ ๊ฐ’์„ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ฆฌํ•˜์‹œ์˜ค.
  2. ๊ฑฐ๋ž˜์ž๊ฐ€ ๊ทผ๋ฌดํ•˜๋Š” ๋ชจ๋“  ๋„์‹œ๋ฅผ ์ค‘๋ณต ์—†์ด ๋‚˜์—ดํ•˜์‹œ์˜ค.
  3. ์ผ€์ž„๋ธŒ๋ฆฌ์ง€์—์„œ ๊ทผ๋ฌดํ•˜๋Š” ๋ชจ๋“  ๊ฑฐ๋ž˜์ž๋ฅผ ์ฐพ์•„์„œ ์ด๋ฆ„์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์‹œ์˜ค.
  4. ๋ชจ๋“  ๊ฑฐ๋ž˜์ž์˜ ์ด๋ฆ„์„ ์•ŒํŒŒ๋ฒณ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด์„œ ๋ฐ˜ํ™˜ํ•˜์‹œ์˜ค.
  5. ๋ฐ€๋ผ๋…ธ์— ๊ฑฐ๋ž˜์ž๊ฐ€ ์žˆ๋Š”๊ฐ€?
  6. ์ผ€์ž„๋ธŒ๋ฆฌ์ง€์— ๊ฑฐ์ฃผํ•˜๋Š” ๊ฑฐ๋ž˜์ž์˜ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜๊ฐ’์„ ์ถœ๋ ฅํ•˜์‹œ์˜ค.
  7. ์ „์ฒด ํŠธ๋žœ์žญ์…˜ ์ค‘ ์ตœ๋Œ“๊ฐ’์€ ์–ผ๋งˆ์ธ๊ฐ€?
  8. ์ „์ฒด ํŠธ๋žœ์žญ์…˜ ์ค‘ ์ตœ์†Ÿ๊ฐ’์€ ์–ผ๋งˆ์ธ๊ฐ€?

5.6.1 ๊ฑฐ๋ž˜์ž์™€ ํŠธ๋žœ์žญ์…˜

๊ฑฐ๋ž˜์ž ๋ฆฌ์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜ ๋ฆฌ์ŠคํŠธ์ด๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");

List<Transaction> transactions = Arrays.asList(
    new Transaction(brian, 2011, 300),
    new Transaction(raoul, 2012, 1000),
    new Transaction(raoul, 2011, 400),
    new Transaction(mario, 2012, 710),
    new Transaction(mario, 2012, 700),
    new Transaction(alan, 2012, 950)
);

๋‹ค์Œ์€ Trader์™€ Transaction์˜ ํด๋ž˜์Šค ์ •์˜๋‹ค.

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Trader {

    private final String name;
    private final String city;

    public Trader(String n, String c) {
        this.name = n;
        this.city = c;
    }

    public String getName() {
        return this.name;
    }

    public String getCity() {
        return this.city;
    }

    public String toString() {
        return "Trader:" + this.name + " in " + this.city;
    }
}

public class Transaction {
	
    private final Trader trader;
    private final int year;
    private final int value;

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return this.trader;
    }

    public int getYear() {
        return this.year;
    }

    public int getValue() {
        return this.value;
    }

    public String toString() {
        return "{" + this.trader + ", " + 
            "year: " + this.year + ", " +
            "value: " + this.value + "}";
    }
}

์ •๋‹ต

  1. 2011๋…„์— ์ผ์–ด๋‚œ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜์„ ์ฐพ์•„ ๊ฐ’์„ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ฆฌํ•˜์‹œ์˜ค.

    1
    2
    3
    4
    5
    
     List<Transaction> tr2011 = 
         transactions.stream()
                     .filter(transaction -> transaction.getYear() == 2011)
                     .sorted(comparing(Transaction::getValue)) // ํŠธ๋žœ์žญ์…˜ ๊ฐ’์œผ๋กœ ์ •๋ ฌ
                     .collect(toList());
    
  2. ๊ฑฐ๋ž˜์ž๊ฐ€ ๊ทผ๋ฌดํ•˜๋Š” ๋ชจ๋“  ๋„์‹œ๋ฅผ ์ค‘๋ณต ์—†์ด ๋‚˜์—ดํ•˜์‹œ์˜ค.

    1
    2
    3
    4
    5
    
     List<String> cities =
         transactions.stream()
                     .map(transaction -> transaction.getTrader().getCity())
                     .distinct() // ๊ณ ์œ  ๋„์‹œ๋งŒ ์„ ํƒ
                     .collect(toList());
    

    distinct() ๋Œ€์‹ ์— ์ŠคํŠธ๋ฆผ์„ ์ง‘ํ•ฉ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” toSet()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    1
    2
    3
    4
    
     Set<String> cities =
         transactions.stream()
                     .map(transaction -> transaction.getTrader().getCity())
                     .collect(toSet());
    
  3. ์ผ€์ž„๋ธŒ๋ฆฌ์ง€์—์„œ ๊ทผ๋ฌดํ•˜๋Š” ๋ชจ๋“  ๊ฑฐ๋ž˜์ž๋ฅผ ์ฐพ์•„์„œ ์ด๋ฆ„์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์‹œ์˜ค.

    1
    2
    3
    4
    5
    6
    7
    
     List<Trader> traders = 
         transactions.stream()
                     .map(Transaction::getTrader) // ํŠธ๋žœ์žญ์…˜์˜ ๋ชจ๋“  ๊ฑฐ๋ž˜์ž ์ถ”์ถœ
                     .filter(trader -> trader.getCity()**.equals("Cambridge")**)
                     .distinct() // ์ค‘๋ณต์ด ์—†๋„๋ก ํ™•์ธ
                     .sorted(comparing(Trader::getName))
                     .collect(toList());
    
  4. ๋ชจ๋“  ๊ฑฐ๋ž˜์ž์˜ ์ด๋ฆ„์„ ์•ŒํŒŒ๋ฒณ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด์„œ ๋ฐ˜ํ™˜ํ•˜์‹œ์˜ค.

    1
    2
    3
    4
    5
    6
    
     String traderStr =
         transactions.stream()
                     .map(transaction -> transaction.getTrader().getName())
                     .distinct() 
                     .sorted() 
                     .reduce("", (n1, n2) -> n1 + n2); // ์ด๋ฆ„์„ ์•ŒํŒŒ๋ฒณ ์ˆœ์œผ๋กœ ์ •๋ ฌ
    

    ์œ„ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ๋ฌธ์ž์—ด์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ์—ฐ๊ฒฐํ•ด์„œ ์ƒˆ๋กœ์šด ๋ฌธ์ž์—ด ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค. ๋”ฐ๋ผ์„œ ํšจ์œจ์„ฑ์ด ๋ถ€์กฑํ•˜๋‹ค. joining()์„ ์ด์šฉํ•ด์„œ ๋” ํšจ์œจ์ ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.(joining์€ ๋‚ด๋ถ€์ ์œผ๋กœ StringBuilder๋ฅผ ์ด์šฉํ•œ๋‹ค.)

    1
    2
    3
    4
    5
    6
    
     String traderStr =
         transactions.stream()
                     .map(transaction -> transaction.getTrader().getName())
                     .distinct()
                     .sorted()
                     .collect(joining());
    
  5. ๋ฐ€๋ผ๋…ธ์— ๊ฑฐ๋ž˜์ž๊ฐ€ ์žˆ๋Š”๊ฐ€?

    1
    2
    3
    4
    5
    6
    
     boolean isTraderInMilan =
         transactions.stream()
                     .anyMatch(transaction -> transaction.getTrader()
                                                         .getCity()
                                                         .equals("Milan"));
     // anyMatch์— ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ „๋‹ฌํ•ด์„œ ๋ฐ€๋ผ๋…ธ์— ๊ฑฐ๋ž˜์ž๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ 
    
  6. ์ผ€์ž„๋ธŒ๋ฆฌ์ง€์— ๊ฑฐ์ฃผํ•˜๋Š” ๊ฑฐ๋ž˜์ž์˜ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜๊ฐ’์„ ์ถœ๋ ฅํ•˜์‹œ์˜ค.

    1
    2
    3
    
     transactions.stream()
                 .filter(t -> t.getTrader().getCity().equals("Cambridge"))
                 .forEach(t -> System.out.println(t.getValue()));
    
  7. ์ „์ฒด ํŠธ๋žœ์žญ์…˜ ์ค‘ ์ตœ๋Œ“๊ฐ’์€ ์–ผ๋งˆ์ธ๊ฐ€?

    1
    2
    3
    4
    
     Optional<Integer> max_value = 
         transactions.stream()
                     .map(Transaction::getValue)
                     .reduce(Integer::max);
    
  8. ์ „์ฒด ํŠธ๋žœ์žญ์…˜ ์ค‘ ์ตœ์†Ÿ๊ฐ’์€ ์–ผ๋งˆ์ธ๊ฐ€?

    1
    2
    3
    4
    
     Optional<Integer> min_value =
         transactions.stream()
                     .map(Transaction::getValue)
                     .reduce(Integer::min);
    

    ๋” ์‰ฌ์šด ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

    1
    2
    3
    
     Optional<Transaction> smallestTransaction =
         transactions.stream()
                     .min(comparing(Transaction::getValue));
    

    ์ŠคํŠธ๋ฆผ์€ ์ตœ๋Œ“๊ฐ’์ด๋‚˜ ์ตœ์†Ÿ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ํ‚ค๋ฅผ ์ง€์ •ํ•˜๋Š” Comparator๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” min๊ณผ max ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

5.7 ์ˆซ์žํ˜• ์ŠคํŠธ๋ฆผ

1
2
3
4
// ๋ฉ”๋‰ด์˜ ์นผ๋กœ๋ฆฌ ํ•ฉ๊ณ„ ๊ณ„์‚ฐ
int calories = menu.stream()
                   .map(Dish::getCalories)
                   .reduce(0, Integer::sum);

์ด ์ฝ”๋“œ์—๋Š” ๋ฐ•์‹ฑ ๋น„์šฉ์ด ์ˆจ์–ด์žˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์ „์— Integer๋ฅผ ๊ธฐ๋ณธํ˜•์œผ๋กœ ์–ธ๋ฐ•์‹ฑํ•ด์•ผ ํ•œ๋‹ค.

1
2
3
4
// !!๋ถˆ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ
int calories = menu.stream()
                   .map(Dish::getCalories)
                   .sum();

์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ sum ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค. map ๋ฉ”์„œ๋“œ๊ฐ€ Stream๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ŠคํŠธ๋ฆผ์˜ ์š”์†Œ ํ˜•์‹์€ Integer์ง€๋งŒ ์ธํ„ฐํŽ˜์ด์Šค์—๋Š” sum ๋ฉ”์„œ๋“œ๊ฐ€ ์—†๋‹ค.

๋‹คํ–‰ํžˆ๋„ ์ŠคํŠธ๋ฆผ์€ ์ŠคํŠธ๋ฆผ API ์ˆซ์ž ์ŠคํŠธ๋ฆผ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ๋ณธํ˜• ํŠนํ™” ์ŠคํŠธ๋ฆผ์„ ์ œ๊ณตํ•œ๋‹ค.

5.7.1 ๊ธฐ๋ณธํ˜• ํŠนํ™” ์ŠคํŠธ๋ฆผ

์ž๋ฐ” 8์—์„œ๋Š” ์„ธ ๊ฐ€์ง€ ๊ธฐ๋ณธํ˜• ํŠนํ™” ์ŠคํŠธ๋ฆผ์„ ์ œ๊ณตํ•œ๋‹ค.

  • int ์š”์†Œ์— ํŠนํ™”๋œ IntStream
  • double ์š”์†Œ์— ํŠนํ™”๋œ DoubleStream
  • long ์š”์†Œ์— ํŠนํ™”๋œ LongStream

๊ฐ๊ฐ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ˆซ์ž ์ŠคํŠธ๋ฆผ์˜ ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” sum, ์ตœ๋Œ“๊ฐ’ ์š”์†Œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” max ๊ฐ™์ด ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์ˆซ์ž ๊ด€๋ จ ๋ฆฌ๋“€์‹ฑ ์—ฐ์‚ฐ ์ˆ˜ํ–‰ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

ํŠนํ™” ์ŠคํŠธ๋ฆผ์€ ์˜ค์ง ๋ฐ•์‹ฑ ๊ณผ์ •์—์„œ ์ผ์–ด๋‚˜๋Š” ํšจ์œจ์„ฑ๊ณผ ๊ด€๋ จ ์žˆ์œผ๋ฉฐ ์ŠคํŠธ๋ฆผ์— ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ˆซ์ž ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งคํ•‘

์ŠคํŠธ๋ฆผ์„ ํŠนํ™” ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ๋Š” mapToInt, mapToDouble, mapToLong ์„ธ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋“ค ๋ฉ”์„œ๋“œ๋Š” map๊ณผ ์ •ํ™•ํžˆ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, Stream ๋Œ€์‹  ํŠนํ™”๋œ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

1
2
3
int calories = menu.stream()                    // Stream<Dish> ๋ฐ˜ํ™˜
                   .mapToInt(Dish::getCalories) // IntStream ๋ฐ˜ํ™˜
                   .sum();

mapToInt ๋ฉ”์„œ๋“œ๋Š” ๊ฐ ์š”๋ฆฌ์—์„œ ๋ชจ๋“  ์นผ๋กœ๋ฆฌ(Integer ํ˜•์‹)๋ฅผ ์ถ”์ถœํ•œ ๋‹ค์Œ์— IntStream์„(**Stream ์•„๋‹˜**) ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ **IntStream ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” sum ๋ฉ”์„œ๋“œ**๋ฅผ ์ด์šฉํ•ด์„œ ์นผ๋กœ๋ฆฌ ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์ŠคํŠธ๋ฆผ์ด ๋น„์–ด์žˆ์œผ๋ฉด sum์€ ๊ธฐ๋ณธ๊ฐ’ 0์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

IntStream์€ max, min, average ๋“ฑ ๋‹ค์–‘ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ๋„ ์ง€์›ํ•œ๋‹ค.

๊ฐ์ฒด ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณต์›ํ•˜๊ธฐ(boxed)

์ˆซ์ž ์ŠคํŠธ๋ฆผ โ†’ ํŠนํ™”๋˜์ง€ ์•Š์€ ์ŠคํŠธ๋ฆผ

ex) IntStream์€ ๊ธฐ๋ณธํ˜•์˜ ์ •์ˆ˜๋งŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

1
2
3
// boxed ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํŠนํ™” ์ŠคํŠธ๋ฆผ์„ ์ผ๋ฐ˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // ์ŠคํŠธ๋ฆผ์„ ์ˆซ์ž ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜
Stream<Integer> stream = intStream.boxed(); // ์ˆซ์ž ์ŠคํŠธ๋ฆผ์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜

๊ธฐ๋ณธ๊ฐ’ : OptionalInt

IntStream์—์„œ๋Š” ์ตœ๋Œ“๊ฐ’์„ ์ฐพ์„ ๋•Œ 0์ด๋ผ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ๊ฐ€ ๋„์ถœ๋  ์ˆ˜ ์žˆ๋‹ค.

โ€˜์ŠคํŠธ๋ฆผ์— ์š”์†Œ๊ฐ€ ์—†๋Š” ์ƒํ™ฉโ€™๊ณผ โ€˜์‹ค์ œ ์ตœ๋Œ“๊ฐ’์ด 0์ธ ์ƒํ™ฉ์„ ์–ด๋–ป๊ฒŒ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ์„๊นŒ? Optional์„ ์‚ฌ์šฉํ•œ๋‹ค.

Optional์„ Integer, String ๋“ฑ์˜ ์ฐธ์กฐ ํ˜•์‹์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ OptionalInt, OptionalDouble, OptionalLong ์„ธ ๊ฐ€์ง€ ๊ธฐ๋ณธํ˜• ํŠนํ™” ์ŠคํŠธ๋ฆผ ๋ฒ„์ „๋„ ์ œ๊ณตํ•œ๋‹ค.

1
2
3
4
// OptionalInt๋ฅผ ์ด์šฉํ•ด์„œ IntStream์˜ ์ตœ๋Œ“๊ฐ’ ์š”์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.
**OptionalInt maxCalories** = menu.stream()
                                  .mapToInt(Dish::getCalories)
                                  .max();

์ด์ œ OptionalInt๋ฅผ ์ด์šฉํ•ด์„œ ์ตœ๋Œ“๊ฐ’์ด ์—†๋Š” ์ƒํ™ฉ์— ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
int max = maxCalories.orElse(1); // ๊ฐ’์ด ์—†์„ ๋•Œ ๊ธฐ๋ณธ ์ตœ๋Œ“๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ • 

5.7.2 ์ˆซ์ž ๋ฒ”์œ„

์ž๋ฐ” 8์˜ IntStream๊ณผ LongStream์—์„œ๋Š” range์™€ rangeClosed๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋‘ ๋ฉ”์„œ๋“œ ๋ชจ๋‘ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์‹œ์ž‘๊ฐ’์„, ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ข…๋ฃŒ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค. range ๋ฉ”์„œ๋“œ๋Š” ์‹œ์ž‘๊ฐ’๊ณผ ์ข…๋ฃŒ๊ฐ’์ด ๊ฒฐ๊ณผ์— ํฌํ•จ๋˜์ง€ ์•Š๋Š” ๋ฐ˜๋ฉด rangeClosed๋Š” ์‹œ์ž‘๊ฐ’๊ณผ ์ข…๋ฃŒ๊ฐ’์ด ๊ฒฐ๊ณผ์— ํฌํ•จ๋œ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅด๋‹ค.

1
2
3
IntStream evenNumbers = IntStream.rangeClosed(1, 100) // [1, 100] ๋ฒ”์œ„
		                 .filter(n -> n % 2 == 0); // ์ง์ˆ˜ ์ŠคํŠธ๋ฆผ
System.out.println(evenNumbers.count());

์œ„ ์ฝ”๋“œ์—์„œ filter๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์‹ค์ œ๋กœ๋Š” ์•„๋ฌด ๊ณ„์‚ฐ๋„ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค(์ค‘๊ฐ„ ์—ฐ์‚ฐ์ด๊ณ  ์ตœ์ข… ์—ฐ์‚ฐ์ธ count๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์ง„ํ–‰๋จ!).

5.7.3 ์ˆซ์ž ์ŠคํŠธ๋ฆผ ํ™œ์šฉ : ํ”ผํƒ€๊ณ ๋ผ์Šค ์ˆ˜

์„ธ ์ˆ˜ ํ‘œํ˜„ํ•˜๊ธฐ

ํ”ผํƒ€๊ณ ๋ผ์Šค ์ˆ˜ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์šฐ์„  ์„ธ ์ˆ˜๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค. ์„ธ ์š”์†Œ๋ฅผ ๊ฐ–๋Š” int ๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด (3, 4, 5)๋ฅผ new int[]{3, 4, 5}๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ข‹์€ ํ•„ํ„ฐ๋ง ์กฐํ•ฉ

์„ธ ์ˆ˜ ์ค‘ a, b ๋‘ ์ˆ˜๋งŒ ์ฃผ์–ด์กŒ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, ๋‘ ์ˆ˜๊ฐ€ ํ”ผํƒ€๊ณ ๋ผ์Šค ์ˆ˜์˜ ์ผ๋ถ€๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ?

a * a + b * b์˜ ์ œ๊ณฑ๊ทผ์ด ์ •์ˆ˜์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

1
Math.sqrt(a*a + b*b) % 1 == 0

์ด๋•Œ x๊ฐ€ ๋ถ€๋™ ์†Œ์ˆซ์  ์ˆ˜๋ผ๋ฉด x % 1.0์ด๋ผ๋Š” ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์†Œ์ˆซ์  ์ดํ•˜ ๋ถ€๋ถ„์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

์ด๊ฒƒ์„ filter์— ๋‹ค์Œ์ฒ˜๋Ÿผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1
filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)

์œ„ ์ฝ”๋“œ์—์„œ a๋ผ๋Š” ๊ฐ’์ด ์ฃผ์–ด์ง€๊ณ  b๋Š” ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ œ๊ณต๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ filter๋กœ a์™€ ํ•จ๊ป˜ ํ”ผํƒ€๊ณ ๋ผ์Šค ์ˆ˜๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ชจ๋“  b๋ฅผ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

์ง‘ํ•ฉ ์ƒ์„ฑ

ํ•„ํ„ฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ข‹์€ ์กฐํ•ฉ์„ ๊ฐ–๋Š” a, b๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด์ œ ๋งˆ์ง€๋ง‰ ์„ธ ๋ฒˆ์งธ ์ˆ˜๋ฅผ ์ฐพ์•„์•ผ ํ•œ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ map์„ ์ด์šฉํ•ด์„œ ๊ฐ ์š”์†Œ๋ฅผ ํ”ผํƒ€๊ณ ๋ผ์Šค ์ˆ˜๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
      .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

b๊ฐ’ ์ƒ์„ฑ

์ด์ œ b๊ฐ’์„ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค. Stream.rangeClosed๋กœ ์ฃผ์–ด์ง„ ๋ฒ”์œ„์˜ ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ์„ ๋ฐฐ์› ๋‹ค.

1
2
3
4
IntStream.rangeClosed(1, 100)
         .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
         .boxed()
         .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

filter ์—ฐ์‚ฐ ๋‹ค์Œ์— rangeClosed๊ฐ€ ๋ฐ˜ํ™˜ํ•œ IntStream์„ boxed๋ฅผ ์ด์šฉํ•ด์„œ Stream๋กœ ๋ณต์›ํ–ˆ๋‹ค.

๊ฐœ์ฒด๊ฐ’ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” IntStream์˜ mapToObj ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ด ์ฝ”๋“œ๋ฅผ ์žฌ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
IntStream.rangeClosed(1, 100)
         .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
         .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

a๊ฐ’ ์ƒ์„ฑ

๋งˆ์ง€๋ง‰์œผ๋กœ a๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. b์™€ ๋น„์Šทํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ a๊ฐ’์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
Stream<int[]> pythagoreanTriples =
    IntStream.rangeClosed(1, 100).boxed()
             .flatMap(a ->
                IntStream.rangeClosed(a, 100)
                         .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
                         .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a+b*b)})
             );

์œ„ ์ฝ”๋“œ์—์„œ ์ŠคํŠธ๋ฆผ a์˜ ๊ฐ’์„ ๋งคํ•‘ํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์˜ ์ŠคํŠธ๋ฆผ์ด ๋งŒ๋“ค์–ด์งˆ ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ flatMap ๋ฉ”์„œ๋“œ๊ฐ€ ์ƒ์„ฑ๋œ ๊ฐ๊ฐ์˜ ์ŠคํŠธ๋ฆผ์„ ํ•˜๋‚˜์˜ ํ‰์ค€ํ™”๋œ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์„ธ ์ˆ˜๋กœ ์ด๋ฃจ์–ด์ง„ ์ŠคํŠธ๋ฆผ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ b์˜ ๋ฒ”์œ„๊ฐ€ 1์ด ์•„๋‹Œ a๋ถ€ํ„ฐ ์‹œ์ž‘ํ•จ์Œ ์ค‘๋ณต์„ ๋ง‰๊ธฐ ์œ„ํ•จ์ด๋‹ค.

์ฝ”๋“œ ์‹คํ–‰

limit์„ ์‚ฌ์šฉํ•˜์—ฌ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์„ธ ์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š” ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ๊ฒƒ์ธ์ง€ ๊ฒฐ์ •ํ•˜๋ฉด ๋œ๋‹ค.

1
2
3
pythagoreamTriples.limit(5)
                  .forEach(t -> 
                       System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

๊ฐœ์„ ํ•  ์ ?

ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” ์ œ๊ณฑ๊ทผ์„ ๋‘ ๋ฒˆ ๊ณ„์‚ฐํ•œ๋‹ค. ๋”ฐ๋ผ์„œ (aa, bb, aa+bb) ํ˜•์‹์„ ๋งŒ์กฑํ•˜๋Š” ์„ธ ์ˆ˜๋ฅผ ๋งŒ๋“  ๋‹ค์Œ์— ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์กฐ๊ฑด์— ๋งž๋Š” ๊ฒฐ๊ณผ๋งŒ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ๋” ์ตœ์ ํ™”๋œ ๋ฐฉ๋ฒ•์ด๋‹ค.

1
2
3
4
5
6
Stream<double[]> pythagoreanTriples2 =
	IntStream.rangeClosed(1, 100).boxed()
                 .flatMap(a -> IntStream.rangeClosed(a, 100))
                 .mapToObj(
                    b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) // ๋งŒ๋“ค์–ด์ง„ ์„ธ ์ˆ˜
                 .filter(t -> t[2] % 1 == 0); // ์„ธ ์ˆ˜์˜ ์„ธ ๋ฒˆ์งธ ์š”์†Œ๋Š” ๋ฐ˜๋“œ์‹œ ์ •์ˆ˜์—ฌ์•ผ ํ•œ๋‹ค.

5.8 ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

์ผ๋ จ์˜ ๊ฐ’, ๋ฐฐ์—ด, ํŒŒ์ผ, ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•

5.8.1 ๊ฐ’์œผ๋กœ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

Stream.of

์ž„์˜์˜ ์ˆ˜๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” ์ •์  ๋ฉ”์„œ๋“œ Stream.of๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
// ๋ฌธ์ž์—ด ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ
Stream<String> stream = Stream.of("Modern ", "Java ", "In ", "Action");
// ์ŠคํŠธ๋ฆผ์˜ ๋ชจ๋“  ๋ฌธ์ž์—ด์„ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ ๋ฌธ์ž์—ด์„ ํ•˜๋‚˜์”ฉ ์ถœ๋ ฅ
stream.map(String::toUpperCase).forEach(System.out::println);

๋‹ค์Œ์ฒ˜๋Ÿผ empty ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์„ ๋น„์šธ ์ˆ˜ ์žˆ๋‹ค.

1
Stream<String> emptyStream = Stream.empty();

๋‹ค์Œ์ฒ˜๋Ÿผ empty ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์„ ๋น„์šธ ์ˆ˜ ์žˆ๋‹ค.

5.8.2 null์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋กœ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

์ž๋ฐ” 9์—์„œ๋Š” null์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ฒด๋ฅผ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์ƒˆ๋กœ์šด ๋ฉ”์†Œ๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.

Stream.ofNullable

์˜ˆ๋ฅผ ๋“ค์–ด System.getProperty๋Š” ์ œ๊ณต๋œ ํ‚ค์— ๋Œ€์‘ํ•˜๋Š” ์†์„ฑ์ด ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด๋Ÿฐ ๋ฉ”์†Œ๋“œ๋ฅผ ์ŠคํŠธ๋ฆผ์— ํ™œ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ null์„ ๋ช…์‹œ์ ์œผ๋กœ ํ™•์ธํ•ด์•ผ ํ–ˆ๋‹ค.

1
2
3
String homeValue = System.getProperty("home");
Stream<String> homeValueStream
	= homeValue == null ? Stream.empty() : Stream.of(value);

Stream.ofNullable์„ ์ด์šฉํ•ด ๋‹ค์Œ์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
Stream<String> homeValueStream
	= Stream.ofNullable(System.getProperty("home"));

null์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ์ŠคํŠธ๋ฆผ๊ฐ’์„ flatMap๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์ƒํ™ฉ์—์„œ๋Š” ์ด ํŒจํ„ด์„ ๋” ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
Stream<String> values =
    Stream.of("config", "home", "user")
          .flatMap(key -> Stream.ofNullable(System.getProperty(key)));

5.8.3 ๋ฐฐ์—ด๋กœ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

Arrays.stream

๋ฐฐ์—ด์„ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” ์ •์  ๋ฉ”์„œ๋“œ Arrays.stream์„ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์ฒ˜๋Ÿผ ๊ธฐ๋ณธํ˜• int๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฐฐ์—ด์„ IntStream์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); // ํ•ฉ๊ณ„๋Š” 41

5.8.4 ํŒŒ์ผ๋กœ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

ํŒŒ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋“ฑ์˜ I/O ์—ฐ์‚ฐ์— ์‚ฌ์šฉํ•˜๋Š” ์ž๋ฐ”์˜ NIO API(๋น„๋ธ”๋ก I/O)๋„ ์ŠคํŠธ๋ฆผ API๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋‹ค. java.nio.file.Files์˜ ๋งŽ์€ ์ •์  ๋ฉ”์„œ๋“œ๊ฐ€ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

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

1
2
3
4
5
6
7
8
9
10
long uniqueWords = 0;
try(Stream<String> lines = 
	Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) 
{
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) // ๊ณ ์œ  ๋‹จ์–ด์ˆ˜ ๊ณ„์‚ฐ
                                              .distinct() // ์ค‘๋ณต ์ œ๊ฑฐ
                                              .count();   // ๋‹จ์–ด ์ŠคํŠธ๋ฆผ ์ƒ์„ฑ
} catch(IOException e) {
	// ํŒŒ์ผ์„ ์—ด๋‹ค๊ฐ€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฒ˜๋ฆฌํ•œ๋‹ค.
}

Files.lines๋กœ ํŒŒ์ผ์˜ ๊ฐ ํ–‰ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ŠคํŠธ๋ฆผ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ŠคํŠธ๋ฆผ์˜ ์†Œ์Šค๊ฐ€ I/O ์ž์›์ด๋ฏ€๋กœ ์ด ๋ฉ”์†Œ๋“œ๋ฅผ try/catch ๋ธ”๋ก์œผ๋กœ ๊ฐ์ŒŒ๊ณ  ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ง‰์œผ๋ ค๋ฉด ์ž์›์„ ๋‹ซ์•„์•ผ ํ•œ๋‹ค.

๊ธฐ์กด์—๋Š” finally ๋ธ”๋ก์—์„œ ์ž์›์„ ๋‹ซ์•˜๋‹ค. Stream ์ธํ„ฐํŽ˜์ด์Šค๋Š” AutoCloseable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ try ๋ธ”๋ก ๋‚ด์˜ ์ž์›์€ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌ๋œ๋‹ค.

5.8.5 ํ•จ์ˆ˜๋กœ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ

์ŠคํŠธ๋ฆผ API๋Š” ํ•จ์ˆ˜์—์„œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋‘ ์ •์  ๋ฉ”์„œ๋“œ Stream.iterate์™€ Stream.generate๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋‘ ์—ฐ์‚ฐ์„ ์ด์šฉํ•ด์„œ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ, ์ฆ‰ ํฌ๊ธฐ๊ฐ€ ๊ณ ์ •๋˜์ง€ ์•Š์€ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

iterate์™€ generate์—์„œ ๋งŒ๋“  ์ŠคํŠธ๋ฆผ์€ ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ์ฃผ์–ด์ง„ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ’์„ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์ œํ•œ์œผ๋กœ ๊ฐ’์„ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฌดํ•œํ•œ ๊ฐ’์„ ์ถœ๋ ฅํ•˜์ง€ ์•Š๋„๋ก ๋ณดํ†ต limit(n) ํ•จ์ˆ˜๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค.

iterate ๋ฉ”์„œ๋“œ

1
2
3
Stream.iterate(0, n -> n + 2)
      .limit(10)
      .forEach(System.out::println);
  • (์ดˆ๊นƒ๊ฐ’ 0, ๋žŒ๋‹ค) ์ธ์ˆ˜

2์”ฉ ์ปค์ง€๋Š” ์—ฐ์‚ฐ์„ ์ง„ํ–‰ํ•˜๊ณ  ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ’์„ ์ƒ์‚ฐํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋์ด ์—†์œผ๋ฏ€๋กœ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ ๋‹ค. โ†’ ์–ธ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ

์ผ๋ฐ˜์ ์œผ๋กœ ์—ฐ์†๋œ ์ผ๋ จ์˜ ๊ฐ’์„ ๋งŒ๋“ค ๋•Œ iterate๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • Quiz. ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด ์ง‘ํ•ฉ

    ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด์€ ์œ ๋ช…ํ•œ ๊ณ ์ „ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฌธ์ œ๋‹ค. ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด์€ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 โ€ฆ ๊ฐ™์€ ์‹์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ์ˆ˜์—ด์€ 0, 1๋กœ ์‹œ์ž‘ํ•˜๋ฉฐ ์ดํ›„์˜ ์ˆซ์ž๋Š” ์ด์ „ ๋‘ ์ˆซ์ž๋ฅผ ๋”ํ•œ ๊ฐ’์ด๋‹ค. ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด์˜ ์ง‘ํ•ฉ๋„ ๋น„์Šทํ•œ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ์ฆ‰, (0, 1), (1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21)โ€ฆ ์ฒ˜๋Ÿผ ์—ฐ์†์ ์ธ ์ˆซ์ž๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

    ์šฐ๋ฆฌ๊ฐ€ ํ•ด์•ผํ•  ์ผ์€ iterate ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด์˜ ์ง‘ํ•ฉ์„ 20๊ฐœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค.

    ์šฐ์„  UnaryOperator๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” iterate ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ (0, 1) ๊ฐ™์€ ์ง‘ํ•ฉ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์šฐ์„  ๋‘ ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด๋กœ ๋Œ€์ถฉ ์ง‘ํ•ฉ์„ ๋งŒ๋“ค์–ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, new int[]{0, 1}๋กœ ํ”ผ๋ณด๋‚˜์น˜์ˆ˜์—ด ์ง‘ํ•ฉ (0, 1)์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ iterate ๋ฉ”์„œ๋“œ์˜ ์ดˆ๊นƒ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

    1
    2
    3
    
      Stream.iterate(new int[]{0, 1}, ???)
            .limit(20)
            .forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
    

    ์œ„ ์ฝ”๋“œ์—์„œ ???๋กœ ํ‘œ์‹œ๋œ ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•ด์•ผ ํ•œ๋‹ค. iterate๋Š” ???์ž๋ฆฌ์— ์ฃผ์–ด์ง€๋Š” ๋žŒ๋‹ค๋ฅผ ์—ฐ์†์ ์œผ๋กœ ์ ์šฉํ•˜๋Š” ํ•จ์ˆ˜๋ผ๋Š” ์‚ฌ์‹ค์„ ๊ธฐ์–ตํ•˜๋ฉฐ ํ€ด์ฆˆ๋ฅผ ํ’€์–ด๋ณด์ž.

    • ์ •๋‹ต

      1
      2
      3
      
        Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0]+t[1]})
              .limit(20)
              .forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
      

์ž๋ฐ” 9์˜ iterate ๋ฉ”์†Œ๋“œ๋Š” ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ์ง€์›ํ•œ๋‹ค.

1
2
3
// 0์—์„œ ์‹œ์ž‘ํ•ด์„œ 100๋ณด๋‹ค ํฌ๋ฉด ์ˆซ์ž ์ƒ์„ฑ ์ค‘๋‹จ
IntStream.iterate(0, n -> n < 100, n -> n + 4)
	.forEach(System.out::println)

iterate ๋ฉ”์†Œ๋“œ๋Š” ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํ”„๋ ˆ๋””์ผ€์ดํŠธ๋ฅผ ๋ฐ›์•„ ์–ธ์ œ๊นŒ์ง€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ธ์ง€์˜ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. filter ๋™์ž‘์œผ๋กœ๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ์„ ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

1
2
3
IntStream.iterate(0, n -> n + 4)
         .filter(n -> n < 100)
         .forEach(System.out::println);

์•ˆํƒ€๊น๊ฒŒ๋„ ์ด์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์—†๋‹ค. ์‹ค์ œ๋กœ ์œ„ ์ฝ”๋“œ๋Š” ์ข…๋ฃŒ๋˜์ง€ ์•Š๋Š”๋‹ค. filter ๋ฉ”์†Œ๋“œ๋Š” ์–ธ์ œ ์ด ์ž‘์—…์„ ์ค‘๋‹จํ•ด์•ผ ํ•˜๋Š”์ง€๋ฅผ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ŠคํŠธ๋ฆผ ์‡ผํŠธ์„œํ‚ท์„ ์ง€์›ํ•˜๋Š” takeWhile์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ด๋ฒ•์ด๋‹ค.

1
2
3
IntStream.iterate(0, n -> n + 4)
         .takeWhile(n -> n < 100)
         .forEach(System.out::println);

generate ๋ฉ”์„œ๋“œ

iterate์™€ ๋น„์Šทํ•˜๊ฒŒ generate๋„ ์š”๊ตฌํ•  ๋•Œ ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ iterate์™€ ๋‹ฌ๋ฆฌ generate๋Š” ์ƒ์‚ฐ๋œ ๊ฐ ๊ฐ’์„ ์—ฐ์†์ ์œผ๋กœ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค. generate๋Š” Supplier๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ƒ์‚ฐํ•œ๋‹ค.

1
2
3
Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);

์ด ์ฝ”๋“œ๋Š” 0๊ณผ 1 ์‚ฌ์ด์—์„œ ์ž„์˜์˜ ๋”๋ธ” ์ˆซ์ž ๋‹ค์„ฏ ๊ฐœ๋ฅผ ๋งŒ๋“ ๋‹ค. ๋‹ค์Œ์€ ์‹คํ–‰ ๊ฒฐ๊ณผ๋‹ค.

1
2
3
4
5
0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241

Math.random์€ ์ž„์˜์˜ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ์ •์  ๋ฉ”์„œ๋“œ๋‹ค. limit๊ฐ€ ์—†๋‹ค๋ฉด ์ŠคํŠธ๋ฆผ์€ ์–ธ๋ฐ”์šด๋“œ ์ƒํƒœ๊ฐ€ ๋œ๋‹ค.

generate๋Š” ์ƒํƒœ๊ฐ€ ์—†๋Š” ๋ฉ”์„œ๋“œ, ์ฆ‰ ๋‚˜์ค‘์— ๊ณ„์‚ฐ์— ์‚ฌ์šฉํ•  ์–ด๋–ค ๊ฐ’๋„ ์ €์žฅํ•ด๋‘์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

์œ„ ์˜ˆ์ œ์—์„œ IntStream์„ ์ด์šฉํ•˜๋ฉด ๋ฐ•์‹ฑ ์—ฐ์‚ฐ ๋ฌธ์ œ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค. IntStream์˜ generate ๋ฉ”์„œ๋“œ๋Š” Supplier ๋Œ€์‹ ์— IntSupplier๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.

1
IntStream ones = IntStream.generate(() -> 1);

IntSupplier ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜๋œ getAsInt๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

1
2
3
4
5
IntStream twos = IntStream.generate(new IntSupplier(){
    public int getAsInt() {
        return 2;
    }
});

์ต๋ช… ํด๋ž˜์Šค์™€ ๋žŒ๋‹ค๋Š” ๋น„์Šทํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์ต๋ช…ํด๋ž˜์Šค๋Š” ๋ฉ”์„œ๋“œ์˜ ์—ฐ์‚ฐ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ ํ•„๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅด๋‹ค.

โ†’ ๋ถ€์ž‘์šฉ์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ๋Š” ์˜ˆ์ œ์ด๋‹ค.

๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ๋ถ€์ž‘์šฉ์ด ์—†์—ˆ๋‹ค.(์ƒํƒœ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ)

1
2
3
4
5
6
7
8
9
10
11
12
13
// ํ”ผ๋ณด๋‚˜์น˜ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก IntSupplier ๊ตฌํ˜„
IntSupplier fib = new IntSupplier() {
    private int previous = 0;
    private int current = 1;
    public int getAsInt() {
        int oldPrevious = this.previous;
        int nextValue = this.previous + this.current;
        this.previous = this.current;
        this.current = nextValue;
        return oldPrevious;
    }
};
IntStream.generate(fib).limit(10).forEach(System.out::println);

IntSupplier ๊ฐ์ฒด๋Š” ๊ธฐ์กด ํ”ผ๋ณด๋‚˜์น˜ ์š”์†Œ์™€ ๋‘ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜์— ์–ด๋–ค ํ”ผ๋ณด๋‚˜์น˜ ์š”์†Œ๊ฐ€ ๋“ค์–ด์žˆ๋Š”์ง€ ์ถ”์ ํ•˜๋ฏ€๋กœ ๊ฐ€๋ณ€ ์ƒํƒœ ๊ฐ์ฒด๋‹ค. getAsInt๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ฐ์ฒด ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋ฉฐ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ƒ์„ฑํ•œ๋‹ค.

iterate๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ๊ฐ ๊ณผ์ •์—์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ƒ์„ฑํ•˜๋ฉด์„œ๋„ ๊ธฐ์กด ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๋Š” ์ˆœ์ˆ˜ํ•œ ๋ถˆ๋ณ€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ–ˆ๋‹ค.

This post is licensed under CC BY 4.0 by the author.