스트림에서는 부작용 없는 함수를 사용하라
스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분이다. 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다.
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 부분은 freq(외부 상태)를 수정하고 있어 문제를 발생한다.
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words.collect(groupingBy(String::toLowerCase, counting()));
}
stream을 잘 사용하면 위와 같다. forEach 연산(종단연산)은 스트림 계산 결과를 보고할 때만 사용하고, 계산할 때는 쓰지말자. 가끔은 스트림 계산 결과를 기존 컬렉션에 추가하는 등 다른 용도로 사용할 수 있다.
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps 에 따르면,
forEach를 병렬로 실행하면 non-thread-safety 으로인해 잘못된 결과가 발생하고 이에 따라 필요한 동기화 로직을 추가하면 병렬 처리의 이점이 손상될 수 있다고 한다.