(Effective Java) 7장 람다와 스트림
아이템 42. 익명 클래스보다는 람다를 사용하라
- 람다의 모든 매개변수 타입은 생략한다.
- 타입을 명시해야 코드가 더 명확한 경우만 명시
- 컴파일러가 “타입을 알 수 없다”는 오류를 낼 때만 명시
- 단, 람다 코드로 명확히 동작을 알 수 없거나 코드 줄 수가 많아지면 람다를 쓰지 않는게 좋다.
- 람다는 이름도 없고 문서화도 못하기 때문.
- 함수 객체가 자기 자신을 참조해야 한다면 반드시 익명 클래스를 써야 한다.
- 람다에서의
this
는 바깥 인스턴스를 가리키는 반면, - 익명 클래스에서의
this
는 인스턴스 자신을 가리킨다.
- 람다에서의
아이템 43. 람다보다는 메서드 참조를 사용하라
- 가독성 측면에서 하는 얘기인데, 꼭 메서드 참조가 깔끔하리라는 법은 없으므로 상황에 맞게 쓰면 된다.
아이템 44. 표준 함수형 인터페이스를 사용하라
- 웬만한 FunctionalInterface는 이미 표준 함수형 인터페이스 로 제공되고 있으므로, 직접 만들지 말고 이거 쓰는게 낫다.
java.util.function
에 총 43개의 인터페이스가 담겨있다.
- 기본 6개 인터페이스만 기억하면 나머지도 유추할 수 있다.
1
2
3
4
5
6
UnaryOperator<T> T apply(T t)
BinaryOperator<T> T apply(T t1, T t2)
Predicate<T> boolean test(T t)
Function<T,R> R apply(T t)
Supplier<T> T get()
Consumer<T> void accept(T t)
- 기본 타입은 기본타입용 인터페이스를 제공하므로, 박싱된 기본 타입을 넣어서 쓰지 말고 그걸 써라.
- 구조적으로 똑같은 표준 함수형 인터페이스가 있더라도, 직접 작성해야 하는 경우가 있다.
- 자주 쓰이며, 이름 자체가 용도를 명확히 설명해 주는 경우
- 이를 구현하면서 반드시 따라야 하는 규약이 있는 경우
- 유용한 디폴트 메서드를 제공해야 하는 경우
아이템 45. 스트림은 주의해서 사용하라
- 지연 평가(lazy evaluation)
- 종단 연산에 쓰이지 않는 데이터 원소는 아예 중간 연산도 타지 않는다. (take(2)로 2개만 취할거면, 2개를 취한 다음에는 더 이상 연산 할 필요가 없으니까.)
- 그냥 하나씩 꺼내서 filter,map 태우는 식으로 동작하기 때문에, for-if문 보다 느릴 것도, 빠를 것도 없다.
- 스트림에 로직을 다 때려 넣다 보면 스트림이 너무 복잡해져 오히려 가독성이 떨어진다.
- 적당한 부분을 메서드로 빼서 간결하게 만드는 것이 좋다.
- 스트림을 반환하는 메서드 이름은 원소의 정체를 알려주는 복수 명사로 쓰는 것을 강력히 추천한다.
1
2
3
static Stream<BigInteger> primes() {
return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
아이템 46. 스트림에서는 Side-Effect 없는 함수를 사용하라
- [CodingKnowledge] - 함수형 프로그래밍 (Fucntional)
- 당연한 얘기지만, forEach로 외부 변수를 건드려서 side-effect를 만들거나 reduce에 다 때려 넣는 식의 코드는 함수형을 잘못 이해하고 사용하는 것이다.
아이템 47. 반환 타입으로는 스트림보다 컬렉션이 낫다
- 왜냐면, 스트림은
Iterable
을 extend하지 않아서 for-each 등에서 사용할 수 없기 때문.Stream<E>
를Iterable<E>
로 중개해주는 어댑터를 사용하면 for-each로 돌릴 수 있긴 하다
- 반대로 Iterable도 마찬가지.
Stream
을 태울 수 없다.- 역시
Iterable<E>
를Stream<E>
타입으로 변환해주는 어댑터를 사용하면 스트림을 태울 수 있다.
- 역시
- 근데 번거롭게 어댑터 쓸 필요 없이 그냥
Collection
을 반환하면, 스트림도 반복도 지원된다!- 하지만 상황에 따라 그냥 스트림이나 Iterable을 반환하는게 더 나을 때도 있음. 컬렉션 구현이 애매한 경우나..
아이템 48. 스트림 병렬화는 주의해서 적용하라
- 데이터 소스가
Stream.iterate
인 경우 성능 개선을 기대하기 어렵다- parallel은 병렬로 실행하기 위해서 데이터 소스를 chunk 단위로 자르는데, iterate는 순차적으로 다음 요소를 반환받는 방식이라 chunk 단위로 자르기 어렵다
- 중간 연산
limit
또는findFirst
같이 요소의 순서에 의존하는 연산을 쓰는 경우 성능 개선을 기대하기 어렵다- limit 대신 rangeClosed를 쓰자.
LongStream.rangeClosed(1, n)
- 특히 limit 같은 것 쓸 때, 소수 찾기 처럼 나중 갈 수록 연산이 오래걸리는 케이스는 더 주의해야 한다.
- 10개의 소수 찾기를 쿼드코어 환경에서 돌린다고 가정하면
- 10번째 소수를 찾는 마지막 연산에서 남는 CPU 코어가 11번째, 12번째, 13번째 소수를 찾는 연산을 시작한 다음 나중에 11,12,13번째 소수를 버린다. 너무 비효율적이고 심각하게 오래걸린다.
- limit 대신 rangeClosed를 쓰자.
- 박싱/언박싱 문제는 성능에 큰 영향을 미친다. 병렬 스트림에 잘 맞는 소스는 다음과 같다.
- 기본형 스트림 (
IntStream
등) - ArrayList, HashMap, HashSet, ConcurrentHashMap
- SplittableRandom
- 보통 ThreadLocalRandom을 쓰는데, 병렬 스트림에는 SplittableRandom을 쓴다.
- 기본형 스트림 (
This post is licensed under CC BY 4.0 by the author.