Stream

๋ฐฑ๊ธฐ์„  ๋‹˜์˜ <๋” ์ž๋ฐ”, Java 8>๋ฅผ ๋ณด๊ณ  ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

1. ๊ฐœ๋…

  • ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ์ €์žฅ์†Œ(์ปฌ๋ ‰์…˜)์ด ์•„๋‹ˆ๋‹ค. ์ฃผ์˜ํ• ๊ฒƒ!

  • Functional in nature - ์ŠคํŠธ๋ฆผ์ด ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ์ดํ„ฐ์†Œ์Šค๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์˜ค์ง ํ•œ๋ฒˆ๋งŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • ๋ฌด์ œํ•œ์ผ์ˆ˜๋„ ์žˆ๋‹ค. - short circuit ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ค‘๊ฐœ ์˜คํผ๋ ˆ์ด์…˜์€ ๊ทผ๋ณธ์ ์œผ๋กœ lazy ํ•˜๋‹ค.

    • ์ค‘๊ฐœ ์˜คํผ๋ ˆ์ด์…˜ : ์ค‘๊ฐ„์—์„œ ์ด์–ด์ค€๋‹ค.

    • ์ข…๋ฃŒ(ํ„ฐ๋ฏธ๋„) ์˜คํผ๋ ˆ์ด์…˜ : ์ข…๋ฃŒ

  • ์†์‰ฝ๊ฒŒ ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

    • parallelStream() ์ดํ›„์— ์ฃผ๋Š” ์˜คํผ๋ ˆ์ดํ„ฐ๋“ค์€ ๋ชจ๋‘ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

    • ์ƒ›๊ธธ

      • ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๊ฐ€ ํ•ญ์ƒ ๋น ๋ฅธ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

      • ๋ฐ์ดํ„ฐ๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€ ๊ฒฝ์šฐ, ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.

      • ์–ด์ฐŒ๋˜์—ˆ๋“  ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋Š” ์“ฐ๋ ˆ๋“œ๋ฅผ ํ•˜๋‚˜ ๋” ๋งŒ๋“œ๋Š” ๋น„์šฉ์ด ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์•ผํ•œ๋‹ค.

2. stream pipeline

  • 0 ๋˜๋Š” ๋‹ค์ˆ˜์˜ ์ค‘๊ฐœ ์˜คํผ๋ ˆ์ด์…˜ (intermediate operation)๊ณผ ํ•œ ๊ฐœ์˜ ์ข…๋ฃ” ์˜คํผ๋ ˆ์ด์…˜(terminal operation) ์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

  • ์ŠคํŠธ๋ฆผ์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋Š” ์˜ค์ง ํ„ฐ๋ฏธ๋„ ์˜คํผ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•  ๋•Œ์—๋งŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

3. ์ค‘๊ฐœ ์˜คํผ๋ ˆ์ด์…˜

  • Stream ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

  • Stateless / Stateful ์˜คํผ๋ ˆ์ด์…˜์œผ๋กœ ์ƒ์„ธํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

    • ๋Œ€๋ถ€๋ถ„์€ stateless

    • distinct, sorted ์ฒ˜๋Ÿผ ์ด์ „ ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•ด์•ผํ•  ๊ฒฝ์šฐ stateful

  • ์˜ˆ์‹œ

    • filter, map, limit, skip, sorted ...

4. ์ข…๋ฃŒ ์˜คํผ๋ ˆ์ด์…˜

  • Stream ์„ ๋ฆฌํ„ดํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜

    • collect, allMatch, count, forEach, min, max ..

5. ์ฐธ๊ณ 

6. ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๊ธฐ

6-1. ๊ฑธ๋Ÿฌ๋‚ด๊ธฐ

  • Filter(Predicate)

6-2. ๋ณ€๊ฒฝํ•˜๊ธฐ

  • Map(Function) ๋˜๋Š” FlatMap(Function)

  • ์˜ˆ) ๊ฐ๊ฐ์˜ Post ์ธ์Šคํ„ด์Šค์—์„œ String title๋งŒ ์ƒˆ๋กœ์šด ์ŠคํŠธ๋ฆผ์œผ๋กœ

  • ์˜ˆ) List<Stream<String>>์„ String์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ

6-3. ์ƒ์„ฑํ•˜๊ธฐ

  • generate(Supplier) ๋˜๋Š” Iterate(T seed, UnaryOperator)

  • ์˜ˆ) 10๋ถ€ํ„ฐ 1์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ๋ฌด์ œํ•œ ์ˆซ์ž ์ŠคํŠธ๋ฆผ

  • ์˜ˆ) ๋žœ๋ค int ๋ฌด์ œํ•œ ์ŠคํŠธ๋ฆผ

6-4. ์ œํ•œํ•˜๊ธฐ

  • limit(long) ๋˜๋Š” skip(long)

  • ์˜ˆ) ์ตœ๋Œ€ 5๊ฐœ์˜ ์š”์†Œ๊ฐ€ ๋‹ด๊ธด ์ŠคํŠธ๋ฆผ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

  • ์˜ˆ) ์•ž์—์„œ 3๊ฐœ๋ฅผ ๋บ€ ๋‚˜๋จธ์ง€ ์ŠคํŠธ๋ฆผ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

6-5. ์ŠคํŠธ๋ฆผ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ํŠน์ • ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ํ™•์ธ

  • anyMatch(), allMatch(), nonMatch()

  • ์˜ˆ) k๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฌธ์ž์—ด์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. (true ๋˜๋Š” false๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.)

  • ์˜ˆ) ์ŠคํŠธ๋ฆผ์— ์žˆ๋Š” ๋ชจ๋“  ๊ฐ’์ด 10๋ณด๋‹ค ์ž‘์€์ง€ ํ™•์ธํ•œ๋‹ค.

6-6. ๊ฐœ์ˆ˜ ์„ธ๊ธฐ

  • count()

  • ์˜ˆ)10๋ณด๋‹ค ํฐ ์ˆ˜์˜ ๊ฐœ์ˆ˜๋ฅผ ์„ผ๋‹ค.

6-7. ์ŠคํŠธ๋ฆผ์„ ๋ฐ์ดํ„ฐ ํ•˜๋‚˜๋กœ ๋ญ‰์น˜๊ธฐ

  • reduce(identity, BiFunction), collect(), sum(), max()

  • ์˜ˆ)๋ชจ๋“  ์ˆซ์ž ํ•ฉ ๊ตฌํ•˜๊ธฐ

  • ์˜ˆ)๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ List ๋˜๋Š” Set์— ์˜ฎ๊ฒจ๋‹ด๊ธฐ


public class StreamTest {

    private List<Food> asianFoods;
    private List<Food> westernFoods;

    @BeforeEach
    void init() {
        asianFoods = new ArrayList<>(){{
            add(new Food(1, "๋น„๋น”๋ฐฅ", true));
            add(new Food(2, "์„ค๋ ํƒ•", false));
            add(new Food(3, "๋ถˆ๋‹ญ๋ณถ์Œ๋ฉด", true));
            add(new Food(4, "๋–ก๋ณถ์ด", true));
            add(new Food(5, "๊ถ์ค‘๋–ก๋ณถ์ด", false));
            add(new Food(6, "์ˆœ๋Œ€๊ตญ", false));
        }};

        westernFoods = new ArrayList<>(){{
            add(new Food(1, "ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€", true));
            add(new Food(2, "๋ชฉ์‚ด ์Šคํ…Œ์ดํฌ", true));
            add(new Food(3, "ํ‹ฐ๋ณธ ์Šคํ…Œ์ดํฌ", false));
            add(new Food(4, "๋ฏธ๊ตญ์‹ ์ง  ํ”ผ์ž", false));
        }};
    }

    @Test
    void ๋–ก๋ณถ์ด_๋ฉ”๋‰ด๋งŒ() {
        List<Food> filtered = asianFoods.stream().filter(s -> s.getName().contains("๋–ก๋ณถ์ด")).collect(Collectors.toList());

        assertThat(filtered).hasSize(2);
        assertThat(filtered).containsAll(
                List.of(new Food(4, "๋–ก๋ณถ์ด", true),
                        new Food(5, "๊ถ์ค‘๋–ก๋ณถ์ด", false)));
    }

    @Test
    void ์ข‹์•„ํ•˜์ง€_์•Š๋Š”_์Œ์‹๋งŒ() {
        List<Food> filtered = asianFoods.stream().filter(f -> !f.isLiked()).collect(Collectors.toList());

        assertThat(filtered).hasSize(3);
        assertThat(filtered).containsAll(List.of(
                new Food(2, "์„ค๋ ํƒ•", false),
                new Food(5, "๊ถ์ค‘๋–ก๋ณถ์ด", false),
                new Food(6, "์ˆœ๋Œ€๊ตญ", false)
        ));
    }

    @Test
    void ์Œ์‹์ด๋ฆ„๋งŒ_๋ชจ์•„์„œ_์ŠคํŠธ๋ฆผ_๋งŒ๋“ค๊ธฐ() {
        List<String> foodNames = asianFoods.stream().map(Food::getName).collect(Collectors.toList());

        assertThat(foodNames).hasSize(6);
        assertThat(foodNames).containsAll(List.of("๋น„๋น”๋ฐฅ", "์„ค๋ ํƒ•", "๋ถˆ๋‹ญ๋ณถ์Œ๋ฉด", "๋–ก๋ณถ์ด", "๊ถ์ค‘๋–ก๋ณถ์ด", "์ˆœ๋Œ€๊ตญ"));
    }

    @Test
    void ์•„์‹œ์•„์™€_์„œ์–‘์Œ์‹์˜_๋ชจ๋“ _์ด๋ฆ„๋งŒ_์ถœ๋ ฅํ•˜๊ธฐ() {
        List<List<Food>> globalFoods = new ArrayList<>(){{
            add(asianFoods);
            add(westernFoods);
        }};


        // with lambda : globalFoods.stream().flatMap(Collection::stream).map(Food::getName).collect(Collectors.toList());
        List<String> foodNames = globalFoods.stream()
                .flatMap(foods -> foods.stream())
                .map(food -> food.getName())
                .collect(Collectors.toList());

        assertThat(foodNames).isEqualTo(List.of(
                "๋น„๋น”๋ฐฅ", "์„ค๋ ํƒ•", "๋ถˆ๋‹ญ๋ณถ์Œ๋ฉด", "๋–ก๋ณถ์ด", "๊ถ์ค‘๋–ก๋ณถ์ด", "์ˆœ๋Œ€๊ตญ", "ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€", "๋ชฉ์‚ด ์Šคํ…Œ์ดํฌ", "ํ‹ฐ๋ณธ ์Šคํ…Œ์ดํฌ", "๋ฏธ๊ตญ์‹ ์ง  ํ”ผ์ž"
        ));
    }

    @Test
    void _10๋ถ€ํ„ฐ_1์”ฉ_์ฆ๊ฐ€ํ•˜๋Š”_๋ฌด์ œํ•œ์ŠคํŠธ๋ฆผ์—์„œ_์ฒ˜์Œ์˜_10๊ฐœ_์ œ์™ธํ•˜๊ณ _์ตœ๋Œ€_10๊ฐœ๊นŒ์ง€๋งŒ_์ถœ๋ ฅ() {
        List<Integer> ints = Stream.iterate(10, i -> i + 1)
                .skip(10)
                .limit(10)
                .collect(Collectors.toList());

        assertThat(ints).isEqualTo(List.of(20, 21, 22, 23, 24, 25, 26, 27, 28, 29));
    }

    @Test
    void ์„œ์–‘์Œ์‹์ค‘์—์„œ_ํ”ผ์ž์ข…๋ฅ˜๊ฐ€_์žˆ๋Š”์ง€_ํ™•์ธ() {
        boolean hasPizza = westernFoods.stream()
                .anyMatch(f -> f.getName().contains("ํ”ผ์ž"));

        assertThat(hasPizza).isTrue();
    }
}

Last updated