이펙티브 자바 3/E_람다와스트림
by Gongdel
7장. 람다와 스트림
자바 8에서 함수형 인터페이스, 람다, 메서드 참조라는 개념이 추가되면서 함수 객체를 더 쉽게 만들 수 있게 되었다.
이와 함께 스트림 API까지 추가되어 데이터 원소의 시퀀스 처리를 라이브러리 차원에서 지원하기 시작했다. 이번 장에서는 이 기능들을 효과적으로 사용하는 방법을 알아보겠다.
42. 익명 클래스보다는 람다를 사용하라.
핵심 정리
자바 8이 되면서 작은 함수 객체를 구현하는 데 적합한 람다가 도입되었다. 익명 클래스는 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때만 사용하라.
람다는 작은 함수 객체를 아주 쉽게 표현할 수 있어 (이전 자바에서는 실용적이지 않던) 함수형 프로그래밍의 지평을 열었다.
43. 람다보다는 메서드 참조를 사용하라.
핵심 정리
메서드 참조는 람다의 간단명료한 대안이 될 수 있다. 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하라.
예 | 메서드 참조 | 람다 |
---|---|---|
정적 | Integer:parseInt | str -> Integer.parseInt(str) |
한정적 | Instant.now()::isAfter | Instant then = Instant.now(); t-> then.isAfter(t); |
비한정적 | String::toLowerCase | str -> str.toLowerCase() |
클래스 | TreeMap<K,V>::new | () -> new TreeMap<K,V> |
배열 | int[]::new | len -> new int[len] |
44. 표준 함수형 인터페이스를 사용하라.
핵심 정리
이제 자바도 람다를 지원한다. 입력값과 반환값에 함수형 인터페이스 타입을 활용하자. 보통은 java.util.function 패키지의 표준 함수형 인터페이스를
사용하는 것이 가장 좋은 선택이다.
단, 흔치는 않지만 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나을 수도 있음을 잊지 말자.
java.util.function 43개의 표준의 함수형 인터페이스가 존재한다.
43개를 모두 알 필요는 없다. 아래 6개의 기본 인터페이스만 알면 나머지 인터페이스를 유추할 수 있다.
기본 인터페이스 | 함수 시그니처 | 예
—- | —- | —-
UnaryOperator
- UnaryOperator : 인수가 1개, 리턴값과 인수 타입이 같은 함수 인터페이스.
- BinaryOperator : 인수가 2개, 리턴값과 인수 타입이 같은 함수 인터페이스.
- Predicate : 인수하나를 받아서, boolean을 리턴하는 함수 인터페이스.
- Function : 인수와 리턴 타입이 다른 함수 인터페이스.
- Supplier : 인수를 받지 않고 값을 리턴하는 함수 인터페이스.
- Consumer : 인수를 1개 받고, 반환값이 없는 함수 인터페이스.
45. 스트림은 주의해서 사용하라.
핵심 정리
스트림을 사용해야 더 알맞거나, 반복 상식이 더 알맞은 일도 있다. 어느 쪽이 나은지가 확연히 드러나는 경우가 많겠지만, 아니더라도 방법은 있다.
스트림과 반복 중 어느 쪽이 나은지 확신하기 어렵다면 둘 다 해보고 더 나은 쪽을 택하라.
스트림을 과용하면 프로그램이 읽거나 유지보수하기 어려워진다.
스트림으로 할 수 없는일
- 코드 블록에서의 범위 안의 지역변수를 수정할 경우, 수정하는 건 불가능하다.(Atomic 종류(AtomicInteger, AtomicBoolean..)는 가능)
- break, continue 문으로 종료하거나, skip하는 작업.
- 내부 로직에서 예외를 던지는 작업.
스트림으로 하기 좋은 작업
- 원소들의 시퀀스를 일관되게 변환한다.
- 원소들의 시퀀스를 필터링한다.
- 원소들의 시퀀스를 하나의 연산을 사용해 결합한다.(더하기, 연결하기, 최소값 구하기 등.)
- 원소들의 시퀀스를 컬렉션에 모은다.
- 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.
스트림으로 하기 어려운 작업
- 파이프라인의 여러 단계를 통과할 때 이 데이터의 각 단계에서의 값들에 동시에 접근하기는 어려운 경우다. 스트림 파이프라인은 일단 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃기 때문이다.
주의할 점
커스텀 스레드풀을 사용하지 않을시, 병렬 스트림 파이프라인도 기본 포크-조인풀에서 수행되므로 다른 시스템의 부분에 성능에 악영향을 줄수 있다. reduce 연산에 사용되는 함수는 결합법칙을 만족하고, 간섭받지 않고, 상태를 갖지 않아야 한다. 랜덤 스트림 연산시 SplittableRandom을 사용하여 성능을 높일수 있다.
46. 스트림에서는 부작용 없는 함수를 사용하라.
핵심 정리
스트림 파이프라인 프로그래밍의 핵심은 부작용 없는 함수 객체에 있다. 스트림뿐 아니라 스트림 관련 객체에 건네지는 모든 함수 객체가 부작용이 없어야 한다. 스트림을 올바로 사용하려면 수집기를 잘 알아둬야 한다. 가장 중요한 수집기 팩터리는 toList, toSet, toMap, groupingBy, joining이다.
47. 반환 타입으로는 스트림보다 컬렉션이 낫다.
핵심 정리
원소 시퀀스를 반환하는 메서드를 작성할 때는 이를 스트림으로 처리하기를 원하는 사용자와 반복으로 처리하길 원하는 사용자가 모두 있을 수 있음을 떠올리고, 양쪽을 다 만족시키려 노력하자.
컬렉션을 반환할 수 있다면 그렇게 하라.
반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하라.
그렇지 않으면 전용 컬렉션을 구현할지 고민하라. 컬렉션을 반환하는 게 불가능하면 스트림과 Iterable 중 더 자연스러운 것을 반환하라.
48. 스트림 병렬화는 주의해서 적용하라.
핵심 정리
계산도 올바로 수행하고 성능도 빨라질 거라는 확신 없이는 스트림 파이프라인 병렬화는 시도조차 하지 말라.
스트림을 잘못 병렬화하면 프로그램을 오동작하게 하거나 성능을 급격히 떨어뜨린다. 병렬화하는 편이 낫다고 믿더라도, 수정 후의 코드가 여전히 정확한지 확인하고 운영 환경과 유사한 조건에서 수행해 보며 성능지표를 유심히 관찰하라.
그래서 계산도 정확하고 성능도 좋아졌음이 확실해졌을 때, 오직 그럴 때만 병렬화 버전 코드를 운영 코드에 반영하라.
Subscribe via RSS