[Effective Java 3/E] ITEM 47. 반환 타입으로는 스트림보다 컬렉션이 낫다

    Effective Java 표지. Source : https://blog.insightbook.co.kr/

    *알림 : *
    Effective Java 3판은 Java 9까지 도입된 언어적 기능을 중심으로 서술되어 있습니다. 10버젼 이후의 Java 개발을 하시는 분들은 우회적인 접근법 대신 Java 언어 내 새로 도입된 기능이 더 간결하고 좋을 수 있습니다.

    해당 포스팅은 SSAFY 내 책읽기 스터디의 활동을 통해 작성된 포스팅입니다.
    https://github.com/kjsu0209/JavaBook
    https://medium.com/javabook

     

    JavaBook – Medium

    Documentation space of our book study.

    medium.com

     

    kjsu0209/JavaBook

    책읽기 스터디. Contribute to kjsu0209/JavaBook development by creating an account on GitHub.

    github.com

    Stream 인터페이스는 Iterable 인터페이스의 추상 메서드를 모드 정의해 놓았지만, Iterable을 확장하지 않았다는 문제가 있다. forEach 구문을 사용하길 원한다면, 아래와 같은 정적 메서드를 이용할 수 있다.

    예제 1 : Stream → Iterable adaptor

    // Adapter from Stream<E> to Iterable<E>
    public static <E> Iterable<E> iterableOf(Stream<E> stream) {
            return stream::iterator;
    }
    
    for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
            // DO WHAT YOU WANT
    }

    반대로 Iterable을 Stream으로도 바꿀 수 있다.

    예제 2 : Iterable → Stream adaptor

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

    위 두 가지 adaptor를 이용해 Stream/forEach 구문으로 처리하고자 하는 프로그래머 집단 모두를 만족할 수 있다. 그러나 adaptor는 코드를 지저분하게 하고, 성능상의 문제도 존재한다. (저자의 컴퓨터에서는 2.3배가 느려진다고 한다.) 그래서 하나의 리턴 타입만을 선택하는 것이 좋아보인다.

    Collection VS Stream

    Collection

    • Collection 인터페이스는 Iterable의 하위타입이고 stream 메서드도 제공하니 일반적으론 Collection을 반환하는 편이 좋다.
    • 하지만 Collection의 각 원소는 메모리에 올라가므로, 시퀀스의 크기가 크다면 고민해보는 것이 좋다.
    • 반환할 시퀀스가 크더라도 표현을 간결하게 하여 전체 시퀀스를 메모리 위에 올리지 않고도 동작하도록 작성할 수 있다면 새 컬렉션을 구현하는 것을 생각해보자. PowerSet은, 각 원소의 인덱스를 비트 필드로 이용하는 아이디어다.

    예제 3 : 전용 컬렉터 구현하기 : PowerSet을 반환하는 Collection

    public class PowerSet {
            public static final <E> Collection<Set<E>> of(Set<E> s) {
                    List<E> src = new ArrayList<>(s);
                    if (src.size() > 30) // size는 < 2^31-1인데, 30이 넘어가면 size()가 작동하지 않는다.
                            throw new IllegalArgumentException("Set too big " + s);
                    return new AbstractList<Set<E>>() {
                            @Override public int size() {
                                    return 1 << src.size(); // 2 to the power srcSize
                            }
                            @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;
                            }
                    };
            }
    }

    Stream

    • Collection 반환 예제와 같이 AbstractCollection을 활용하여 Collection을 리턴할 경우에는 contains와 size를 반환하여야 한다. 그러나 contains와 size를 시퀀스의 내용을 확정하기 전 까지 구할 수 없는 경우(i.e. 실제 반복을 돌려보기 전 까지는 무엇이 얼마나 들어갈지 예측이 불가능한 경우)에는 Stream을 반환하는 것이 좋다.

    댓글