woowacourse-study / 2022-modern-java-in-action

우아한테크코스 4기 모던 자바 인 액션 스터디
10 stars 4 forks source link

무한스트림과 가변, 불변 상태 #41

Open yh20studio opened 2 years ago

yh20studio commented 2 years ago

문제

무한 스트림을 생성하는 방법에는 iterate 와 generate가 있다. 이 둘의 차이점은 무엇이며, 책의 후반부에 나오는 피보나치 수열을 만들 때 가변 상태 객체와 불변 상태 객체를 활용하는 차이점에 대해서 알아보자

선정 배경

무한 스트림을 생성하는 두가지 방법의 차이점을 알아보고, 책의 앞장 부터 나왔던 병렬성과 불변 상태 객체에 대해서 좀 더 이해해 보기 위해서

관련 챕터

[5장] 스트림 활용 190~195p

yh20studio commented 2 years ago

첫번째로 보이는 차이점은 각 메서드의 파리미터에 있다.

Stream.iterate()

스크린샷 2022-03-29 오후 3 35 29

Stream.generate()

스크린샷 2022-03-29 오후 3 36 39

차이점은 iterateUnaryOperator로 초기값과 조건을 이용하여 연산을 하면서 스트림을 만들어 나간다. 하지만 generateSupplier를 통해서 주어진 값을 활용해서 스트림을 만든다.

피보나치 수열

그렇다면 책의 예제처럼 피보나치 수열을 만드는 과정을 각각의 메서드를 활용해보자

Supplier<Integer> fib = new Supplier<>() {
    private int previous = 0;
    private int current = 1;

    @Override
    public Integer get() {
        int oldPrevious = this.previous;
        int nextValue = this.previous + this.current;
        this.previous = this.current;
        this.current = nextValue;
        return oldPrevious;
    }
};

@Test
void test() {
    List<Integer> iterateList = Stream.iterate(new int[]{0, 1}, t -> t[0] <= 514229,
                    t -> new int[]{t[1], t[0] + t[1]})
            .map(t -> t[0])
            .sorted()
            .collect(Collectors.toList());

    List<Integer> generateList = Stream.generate(fib)
            .takeWhile(t -> t <= 514229)
            .sorted()
            .collect(Collectors.toList());

    assertThat(iterateList.equals(generateList)).isTrue();
}

// iterate()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229]

// generate()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229]

병렬 스트림으로 처리한다면?

@Test
void parallelTest() {
    List<Integer> iterateList = Stream.iterate(new int[]{0, 1}, t -> t[0] <= 514229,
                    t -> new int[]{t[1], t[0] + t[1]})
            .parallel()
            .map(t -> t[0])
            .sorted()
            .collect(Collectors.toList());

    List<Integer> generateList = Stream.generate(fib)
            .parallel()
            .takeWhile(t -> t <= 514229)
            .sorted()
            .collect(Collectors.toList());

    assertThat(iterateList.equals(generateList)).isTrue();
}
// 정상적인 피보나치 수열 출력
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229]

// iterate()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229]

// generate() 실행할 때마다 다르게 나온다.
// [0, 1, 1, 1, 2, 3, 5, 8, 8, 13, 21, 21, 34, 55, 89, 144, 233, 233, 377, 610, 987, 1597, 2584, 2584, 4181, 6765, 10946, 17711, 28657, 28657, 46368, 75025, 75025, 121393, 196418, 196418, 317811, 514229, 514229]
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 46368, 75025, 121393, 196418, 317811, 514229]

결론

iterate()는 초기값과 연산을 지정할 수 있기에 람다로 내부에서 객체를 불변 상태로 유지했다. 따라서 병렬로 스트림을 처리해도 같은 값이 나왔다. 하지만 generate()는 연산을 지정할 수 없기 때문에 익명 클래스로 외부에서 값을 변경하면서 수열을 만들기에 병렬로 스트림을 처리했을 때 부작용이 일어난 것이다.

물론 피보나치 수열을 만드는데 generate()를 사용하는 것이 좋지 못한 사용법이다. 하지만 이로 간접적으로 병렬 처리의 부작용을 막기 위해 스트림에서는 익명 클래스보다는 상태를 바꾸지 않는 람다를 사용하는 것을 권장하는 것을 알 수 있다.