Item 46 : 스트림에서는 부작용 없는 함수를 사용하라
2021. 8. 14. 13:23
- 스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분이다.
- 이때 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다.
- 순수 함수란 오직 입력만이 결과에 영향을 주는 함수를 말한다.
- 다른 가변 상태를 참조하지 않고 함수 스스로도 다른 상태를 변경하지 않아야 하는데
- 이렇게 하려면 스트림 연산에 건네는 함수 객체는 모든 부작용이 없어야 한다.
//잘못 사용한 스트림
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()){
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
}
- 스트림, 람다, 메소드 참조를 사용했지만 스트림코드라고 부를 수 없다.
- 스트림 API의 이점을 사릴지 못하여 같은 기능의 반복적 코드보다 길고, 읽기 어렵고, 유지보수에도 좋지않다.
- 이 코드의 모든 작업이 종단연산인 forEach에서 일어나면서 외부 타입 변경시에 문제가 생길 수 있다.
//바르게 고친 스트림
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()){
freq = words
.collect(groupingBy(String::toLowerCase, counting()));
}
forEach 연산은 스트림 계산 결과를 보고할 때만 사용하고 계산하는 데는 사용하지 말자
Collector(수집기)
- collector는 스트림의 원소들을 객체 하나에 취합한다는 듯이다.
- 수집기가 생성하는 객체는 일반적으로 컬렉션이다.
- 수집기는 총 3가지이다.
- toList()
- toSet()
- toCollection
//빈도표에서 가장 흔한 단어 10개를 뽑아내는 스트림 파이프라인
List<String> topTen = freq.keySet().stream()
.sorted(comparing(freq::get).reversed())
.limit(10)
.collect(toList());
- ket 추출 함수로 쓰인 freq::get은 입력받은 단어를 빈도표에서 찾아 그 빈도를 반환한다.
- reverse는 정렬이다.(내림차순)
//가장 간단한 맵 수집기 toMap(keyMapper, valueMapper)
//toMap 수집기를 사용하여 문자열을 열거 타입 상수에 매핑
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect()
toMap(Object:;toString, e->e));
- 열거 타입 상수의 문자열 표현을 열거 타입 자체에 매핑하는 fromString을 구현한 것
- 스트림의 각 원소가 고유한 키에 매핑되어 있을 때 적합하다.
요소를 그룹핑해서 수집
- 스트림은 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다.
- collect()를 호출할 때 Collectors의 groupingBy()또는 groupingByConcurrent()가 리턴하는 Collector를 매개값으로 대입하면 된다.
- groupingBy()는 스레드에 안전하지않은 Map을 생성한다.
- groupingByConcurrent()는 스레드에 안전한 ConcurrentMap을 생성한다.
다음 코드는 학생들을 성별로 그룹핑하고 나서, 같은 그룹에 속하는 학생 List를 생성한 후, 성별을 키로, 학생 List를 값으로 갖는 Map을 생성한다.
//1. 전체 학생 List에서 Stream을 얻는다.
Stream<Student> totalStream = totlaList.stream();
//2. Student를 Student.Sex로 매핑하는 Function을 얻는다.
Function<Student, Student.Sex> classifier = Student :: getSetx;
//3. Student.Sex가 키가 되고 그룹핑된 List<Student>가 값인 Map을 생성하는 Collector를 얻는다.
Collector<Strudent, ?, Map<Student.Sex, List<Student>>> collector=
Collectors.groupingBy(classifier);
//4. Stream의 collect() 메소드로 Student를 Student.Sex별로 그룹핑해서 Mpa을 얻는다.
Map<Student.Sex, List<Student>> mapBySex = totlaStream.collect(collector);
//위의 코드를 간단히 표현 한 식
Map<Student.Sex, List<Student>> mapBySex = totlaList.stream()
.collect(Collectors.GroupingBy(Student :: getsex));
정리
- 스트림 파이프라인 프로그래밍의 핵심은 부작용 없는 함수 객체에 있다.
- 스트림 뿐만 아니라 스트림 관련 객체에 건네지는 모든 함수 객체가 부작용이 없어야한다.
- forEach는 출력용으로만 사용하라
- 스트림을 올바로 사용하려면 수집기를 잘 알아둬야 한다.
- 가장 중요한 수집기 패곹리는 toList, toSet, tomap, groupingBy, joining이다.
'책 > 이펙티브자바' 카테고리의 다른 글
Item 48 : 스트림 병렬화는 주의해서 적용하라 (0) | 2021.08.16 |
---|---|
Item 47 : 반환 타입으로는 스트림보다 컬렉션이 낫다 (0) | 2021.08.16 |
Item 45 : 스트림은 주의해서 사용하라 (0) | 2021.08.13 |
Item 44 : 표준 함수형 인터페이스를 사용하라 (0) | 2021.08.12 |
Item 43 : 람다보다는 메소드 참조를 사용하라 (0) | 2021.08.11 |