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

문법식·2022년 10월 4일
0

Effective Java 3/E

목록 보기
46/52

스트림은 처음 봐서는 이해하기 어려울 수 있다. 스트림은 그저 또 하나의 API가 아닌, 함수형 프로그래밍에 기초한 패러다임이기 때문이다. 스트림이 제공하는 표현력, 속도, 병렬성을 얻으려면 API뿐만 아니라 패러다임까지 함께 받아들여야 한다.

스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분이다. 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다. 순수 함수란 오직 입력만이 결과에 영향을 주는 함수를 말한다. 다른 가변 상태를 참조하지 않고, 함수 스슬도 다른 상태를 변경하지 않는다. 이렇게 하려면 스트림 연산에 건네는 함수 개게는 모두 side effect가 없어야 한다.

forEach 연산은 종단 연산 중 기능이 가장 작고 '덜' 스트림답다. 대놓고 반복적이라 병렬화할 수도 없다. forEach 연산은 스트림 계산 결과를 보고할 때만 사용하고, 계산하는 데는 쓰지 말자.

스트림을 사용하려면 수집기(collector) 개념을 꼭 배워야 한다. 수집기는 총 세 가지로, toList(), toSet(), toCollection(collectionFactory)이다.
toMap(keyMapper, valueMapper)는 스트림 원소를 키에 매핑하는 함수와 값에 매핑하는 함수를 인수로 받는다. toMap의 형태는 스트림의 각 원소가 고유한 키에 매핑되게 사용할 수 있다. toMap에서 스트림 원소 다수가 같은 키를 사용한다면 병합 함수를 사용하여 충돌을 피할 수 있다. 인수 3개를 받는 toMap은 어떤 키와 그 키에 연관된 원소들 중 하나를 골라 연관 짓는 맵을 만들 때 유용하다. 인수가 3개인 toMap은 충돌이 나면 마지막 값을 취하는 수집기를 만들 때도 유용하다.
groupBy는 입력으로 분류 함수(classifier)를 받고 출력으로는 원소들을 카테고리별로 모아 놓은 맵을 담은 수집기를 반환한다. 분류 함수는 입력받은 원소가 속하는 카테고리를 반환한다. 그리고 이 카테고리가 해당 원소의 맵 키로 쓰인다. groupingBy가 반환하는 수집기가 리스트 외의 값을 갖는 맵을 생성하게 하려면, 분류 함수와 함께 다운스트림 수집기도 명시해야 한다. 다운스트림 수집기의 역할은 해당 카테고리의 모든 원소를 담은 스트림으로부터 값을 생성하는 일이다. 이 매개변수를 사용하는 가장 간단한 방법은 toSet()을 넘기는 것이다. toSet() 대신 toCollection(collectionFactory)를 건네는 방법도 있는데, 이렇게 하면 리스트나 집합 대신 컬렉션을 값으로 갖는 맵을 생성한다. 원하는 컬렉션 타입ㅇ르 선택할 수 있는 유연성이 생기는 부차적 효과도 있다. 다운스트림 수집기로 counting()을 넘기는 방법도 있다. 이렇게 하면 각 카테고리에 속하는 원소의 개수와 매핑한 맵을 얻는다.
joining은 문자열 등의 CharSequence 인스턴스의 스트림에만 적용할 수 있다. 매개변수가 없으면 단순히 원소들을 연결하는 수집기를 반환한다. 인수 하나짜리 joiningCharSequence 타입의 구분문자를 매개 변수로 받는다. 인수 3개짜리 joining은 구분문자에 더해 prefixsuffix도 받는다.

스트림 파이프라인 프로그래밍의 핵심은 부작용 없는 함수 객체에 있다. 종단 연산 중 forEach는 스트림이 수행한 계산 결과를 보고할 때만 사용해야 하다. 스트림을 올바로 사용하려면 수집기를 잘 알아둬야 한다. 글로 정리하긴 했지만 책을 참고하거나 java.util.stream.CollectorsAPI 문서를 읽어보길 추천한다.

profile
백엔드

0개의 댓글