Item 46. 스트림에서는 부작용 없는 함수를 사용하라

Posted by yunki kim on March 2, 2022

  스트림 패러다임의 핵심은 계산을 일련의 변환(transformation)으로 재구성하는 것이다. 여기서 각 변환은 가능한 이전 단계의 결과를 받아서 처리하는 순수 함수여야 한다. 이를 위해서는 스트림 연산에 건네는 함수 객체는 모두 side-effect가 없어야 한다.

  아래의 예시를 보자.

1
2
3
4
5
6
7
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    })
}
 
cs

  이 코드는 스트림을 가장한 반복적 코드다. 위에서 명시했듯 스트림에서 사용하는 함수는 순수 함수여야 한다. 하지만 위 코드는 freq에 값을 추가하기에 스트림의 이점을 잘 살리지 못했다. 따라서 이런 코드는 단순 반복 코드보다 길고, 가독성이 떨어지고, 유지보수에 좋지 않다. 이 코드의 모든 연산은 종단 연산에서 발생하는 데, 이때 외부 상태를 수정하는 람다를 실행하면 문제가 발생한다.

  forEach 연산은 대놓고 반복적이라 병렬화 할 수 없으며 종단 연산 중 가장 스트림 답지 않다. 따라서 스트림 연산 결과를 보고할 때만 사용하자.

  forEach를 사용하는 대신 collector를 사용하라. 아래 예시는 위 코드를 collector를 사용해 바꾼 것이다.

1
2
3
4
5
6
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    freq = words
        .collect(groupingBy(String::toLowerCase, counting()));
}
 
cs

 

출처 - 이펙티브 자바