Java

[Effective Java] 7-3. 람다와 스트림

noahkim_ 2024. 12. 31. 18:30

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.

 

1. 반환 타입으로는 스트림보다 컬렉션이 낫다

원소 시퀀스 반환시, 스트림 처리와 반복 처리 모두를 만족시키도록 노력하자

스트림은 반복을 지원하지 않음
  • Iterable 확장 X
  • 반복문에서 사용하기 어려움
  • 그러나, Iterable이 정의한 추상 메서드를 모두 정의함

 

Collection이나 그 하위타입을 쓰자

  • Iterable의 하위타입
  • stream 메서드도 지원함

 

하위 타입 사용 시 주의점
  • 메모리가 큰 시퀀스를 무작정 메모리에 올리면 안됨

 

멱집합 원소 배열
public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
        List<E> src = new ArrayList<>(s);
        if (src.size() >= 30) throw new IllegalArgumentException("too many elements");
        
        return new AbstractList<Set<E>>() {
        	@Override public int size() { return 1 << src.size(); }
            
            @Override public boolean contains(Object o) { 
            	return o instanceof Set && src.containsAll((Set) o); 
            }
            
            @Override public Set<E> get(int idx) {
            	Set<E> result = new HashSet<>();
                for (int i = 0; idx != 0; i++, idx >>= 1) {
                	if ((idx & 1) == 1) result.add(src.get(i));                                        
                }
                
                return result;
            }
        }
    }
}
  • 멱집합을 구성하는 각 원소의 인덱스를 비트 벡터로 사용
  • AbstractList를 이용하여 전용 컬렉션 구현하기

 

2. 스트림 병렬화는 주의해서 적용하라

parallel()

  • 스트림 파이프라인을 병렬 실행할 수 있는 스트림 반환
  • 여러 스레드에서 데이터를 처리하여 성능을 향상시킬 수 있음

 

주의사항
  • 작은 데이터에 사용하면 오버헤드로 인해 오히려 성능이 저하될 수 있음
  • 순서를 보장하지 않음
  • 공유 상태 사용 주의
  • CPU 코어를 활용하므로, 코어 수보다 병렬 작업이 크지 않아야 함

 

적합성 판단 기준

Spliterator
  • 데이터 소스를 나누는 작업
  • Stream이나 Iterable의 spliterator 메서드로 얻어올 수 있음
  • 성능을 좌우하는 가장 큰 요소

 

데이터 소스
  • 효율적인 소스
    • 나누는 작업이 수월함 
    • ArrayList, HashMap, HashSet, ConcurrentHashMap
    • int 스트림, long 스트림
    • 배열
  • 효율이 낮은 소스
    • Stream.iterate와 같은 스트림
    • limit를 사용 (순차적 처리 필요)
    • 연결 리스트 (Random Access 필요)

 

참조 지역성
  • 메모리 접근이 순차적으로 이루어질수록 캐시 효율이 높아져 성능이 좋아짐
  • 다량의 데이터를 처리하는 벌크 연산에 중요

 

적합한 연산
  • reduce 연산 시, 효과가 가장 좋음

 

안전 실패

  • 스트림을 잘못 병렬화 하면, 성능이 나빠질 뿐만 아니라 잘못된 결과를 얻어올 수 있음

 

Random 처리

  • 무작위 수들로 이뤄진 스트림을 병렬화 할 때, ThreadLocalRandom보단 SplittableRandom을 사용

 

소수 계산 스트림 파이프라인
LongStream.rangeClosed(2, n)
    .mapToObj(BigInteger::valueOf)
    .filter(i -> i.isProbablePrime(50))
    .count();