이펙티브 자바의 아이템46. '스트림에서는 부작용 없는 함수를 사용하라' 부분을 읽고 정리하는 포스팅.✍️
아이템46. 스트림에서는 부작용 없는 함수를 사용하라
스트림 패러다임의 핵심: 계산을 일련의 변환(transformation)으로 재구성 하는 부분.
이때 각 변환 단계는 이전 단계의 결과를 받아 처리하는 순수 함수인 것이 좋다.
순수 함수: 다른 가변 상태를 참조하지 않고, 함수 스스로도 다른 상태를 변경하지 않는다.
스트림 연산에 쓰이는 함수를 순수 함수로 만드려면?
중간 단계든 종단 단계든 스트림 연산에 건네는 함수 객체가 side effect
가 없어야 한다.
ex) 스트림 패러다임을 이해하지 못하고 사용한 코드 예시
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
}
위 코드가 문제인 이유는 스트림에 전달하는 함수 객체가 스트림 바깥의 freq의 상태를 변경하기 때문이다.
위 코드는 스트림, 람다, 메서드 참조를 사용했고 결과도 잘 나오지만, 스트림 코드라고 볼 수 없다.
스트림 코드를 가장한 반복적 코드
이다.
오히려 그냥 반복문보다 더 길고, 읽기 어렵고(이건 좀 주관적인듯...), 유지보수에 좋지 않음
ex) 스트림을 올바르게 작성한 예시
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words
.collect(groupingBy(String::toLowerCase, counting()));
}
위의 잘못된 코드와 같이 파일을 읽어서 알파벳 별로 숫자를 세는 일을 수행하지만, 스트림 API를 제대로 사용한 코드다.
위의 잘못된 코드 예시는 자바 프로그래머들이 쉽게 실수하는 부분인데, 스트림의 forEach
연산과 자바의 for-each
반복문이 닮아있기 때문이다.
forEach
연산은 스트림 연산중에 가장 스트림스럽지 못한 연산이다. 대놓고 반복적이라서 병렬화도 불가능하다.
forEach
연산은 스트림 계산 결과를 보고할 때만 사용하고, 계산하는 데는 쓰지 말자.
스트림 Collertor
주로 스트림의 종단 연산에 사용되는 수집기
이다.Collectors
클래스의 메서드 갯수는 39개나 되지만, 다행히 복잡한 세부 내용을 잘 몰라도 이 API의 장점을 대부분 활용 가능하다.
수집기가 생성하는 객체는 주로 컬렉션이며 그래서 Collector
라는 이름을 쓴다.
수집기 종류
toList()
: 스트림의 요소를 모아서, List 객체를 반환
toSet()
: 스트림의 요소를 모아서, Set 객체를 반환
toCollection(collectionFactory)
: 스트림의 요소를 지정한 구현 객체로 반환받을 수 있다.
코드 예시
List<String> topTen = freq.keySet().stream()
.sorted(comparing(freq::get).reversed())
.limit(10)
.collect(toCollection(LinkedList::new));
원래는 종단 부분에 Collectors.toCollection(LinkedList::new)
가 들어가야 맞지만, 보통 가독성을 위해 정적 임포트 해서 사용한다.
toMap(keyMapper, valueMapper)
: 스트림의 원소를 키에 매핑하는 함수와 값에 매핑하는 함수를 인수로 받아서, Map 객체를 반환
코드 예시
private Map<String, Car> getCarMap(List<Car> cars) {
return cars.stream()
.collect(toMap(Car::getName(), car -> car));
}
마지막 쓴 값을 취하는 수집기
private Map<String, Car> getCarMap(List<Car> cars) {
return cars.stream()
.collect(toMap(Car::getName(), car -> car, (oldObj, newObj) -> newObj));
}
groupingBy()
: 특정 기준으로 분류하여 Map으로 객체를 반환 => sql의 group by
문과 같음
코드 예시
private Map<String, List<Car>> getCarGroupByBodyStyle(List<Car> cars) {
return cars.stream()
.collect(groupingBy(Car::getBodyStyle, toList()));
}
등등 다양한 컬렉터를 사용할 수 있다.
끗!
'Java' 카테고리의 다른 글
타입 안전 이종 컨테이너 (0) | 2022.04.05 |
---|---|
멤버 클래스는 되도록 static으로 (2) | 2022.03.30 |
표준 함수형 인터페이스 (0) | 2022.03.08 |
Collections.unmodifiableXX()는 방어적 복사가 아니다 (1) | 2022.03.06 |
Collection의 깊은 복사와 방어적 복사 (2) | 2022.03.05 |