Java/Effective Java 3E

[이펙티브자바 3판] ITEM47. 반환타입으로 스트림보다 컬렉션이 낫다

잭피 2020. 12. 13. 11:19

이번장의 핵심은...

컬렉션을 반환하는 게 불가능하면 스트림과 Iterable 중 더 자연스러운 것을 반환하자

만약 나중에 스트림 인터페이스가 Iterable을 지원하도록 자바가 수정된다면,

그때는 안심하고 스트림을 반환하면 됩니다


스트림 반복문

스트림은 반복을 지원하지 않습니다

iterable 인터페이스가 정의한 추상메서드들을 전부 포함하지만 extend 하지 않습니다 

따라서 for-each로 반복하지 못합니다

 

스트림으로 반복문을 쓰고 싶다면 어떻게 해야할까요?

Stream<E>를 Iterable<E>로 중개해주는 어댑터를 생성해주면 됩니다

public static <E> Iterable<E> iterableOf(Stream<E> stream){
    return stream::iterable;
}

하지만 너무 난잡하고 직관성이 떨어집니다

 

Iterable로 스트림을 쓰고 싶다면 어떻게 해야할까요?

Iterable<E>를 Stream<E>로 중개해주는 어댑터를 생성해줍니다

public static Iterable<E> streamOf(Iterable<E> iterable){
    return StreamSupport.stream(iterable.spliterator(), false);
}

이것도 너무 난잡하고 직관성이 떨어집니다

 

객체 시퀀스를 반환하는 메서드를 작성할 때,

메서드가 오직 스트림 파이프라인에서만 쓰이면 마음놓고 Stream을 반환하면 됩니다

하지만 for-each를 사용하는 개발자와 스트림을 사용하는 개발자 모두 배려하여 스트림, iterable을 동시에 제공하는 것이 좋습니다 

 

Collection 인터페이스는 Iterable의 하위 타입이고, stream 메서드도 제공하니 반복과 스트림을 동시에 지원합니다

따라서 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로 최선입니다

 

하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안됩니다

 

컬렉션 내의 시퀀스가 크면 전용 컬렉션을 구현하자

반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet 같은 표준 컬렉션 구현체를 반환하는게 최선입니다

하지만, 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려선 안됩니다

 

예를 들어 주어진 집합의 멱집합을 반환하는 상황을 봅시다

{a,b,c}의 멱집합 -> {{},{a},{b},{c},{a,b},{b,c},{a,c},{b,c},{a,b,c}} 2^3개

원소 개수가 n이면 2^n승개

 

표준 컬렉션 구현체에 저장하려는 생각은 위험합니다

원소가 20개인 집합이 입력으로 들어오면 2^20개를 반환해야합니다

이런 경우 인덱스를 이진수로 표현하는 AbstractList를 이용한 전용 컬렉션을 반환하는게 최선입니다

(각 원소의 인덱스를 비트 벡터로 사용하여 표현)

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("집합에 원소가 너무 많습니다(최대 30개).: " + s);
       }

       return new AbstractList<Set<E>>() {
           @Override
           public int size() {
               return 1 << src.size(); // 원본집합의 2의 원소수의 거듭제곱을 의미
           }

           @Override
           public boolean contains(Object o) {
               return o instanceof Set && src.containsAll((Set) o);
           }

           @Override
           public Set<E> get(int index) {
               Set<E> result = new HashSet<>();
               for (int i = 0; index != 0; i++, index >>=1) {
                   if((index & 1) == 1) {
                       result.add(src.get(i));
                   }
               }
               return result;
           }
       };
    }
}

size 예외처리 

size() 메서드의 리턴타입은 int이기 때문에 최대길이는 2^31 - 1 또는 Integer.MAX_VALUE로 제한됩니다 

→ Collection을 쓸 때의 단점입니다 (스트림, Iterable은 size에 대한 고민이 필요없음)

 

이런 전용컬렉션을 구현할때 주의할점은 contains함수와 size함수를 반드시 구현해야합니다
이 두개를 구현하는게 불가능한 상황에서는 스트림이나 Iterable을 반환하는게 낫습니다
어쨌든 이런 전용 컬렉션을 구현하면 스트림보다 1.4배 (저자 컴퓨터기준)정도는 빨랐고
어댑터는 스트림보다 2.3배(저자컴퓨터기준)느리다고 합니다

 

 

이 글은 “이펙티브 자바 3판” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “shk3029@kakao.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.