본문 바로가기

Effective Java

[Effective Java] 아이템 47 - 반환 타입으로는 스트림보다 컬렉션이 낫다

 

■ 반환타입 선택

원소 시퀀스, 즉 일련의 원소를 반환하는 메서드는 많다. Collection, List, Set과 같은 컬렉션 인터페이스, 혹은 Iterable이나 배열을 사용했다. 자바 8에서는 스트림이 도입되면서 선택지가 복잡해졌다.

 

스트림은 반복(Iteration)을 지원하지 않는다. 따라서 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다. 여기서 재밌는 사실 하나는 Stream인터페이스는 Iterable 인터페이스의 추상 메서드를 모두 정의해 놓았지만 Iterable을 확장하지 않았다는 문제가 있다. 

 

예제 1 : Stream → Iterable adaptor


아래의 코드는 자바 타입 추론의 한계로 컴파일되지 않는다.
public class Item47 {
    public static void main(String[] args) {
        for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) { // 컴파일 에러가 난다.
            // 프로세스를 처리한다.
        }
    }
}​

Stream의 iterator 메서드에 메서드 참조를 건네면 될 것 같지만 컴파일 오류가 난다.



스트림을 반복하기 위한 '끔찍한' 우회방법

public class Item47 {
    public static void main(String[] args) {
        for (ProcessHandle ph : (Iterable<ProcessHandle>) 
        			ProcessHandle.allProcesses()::iterator) {
            // 프로세스를 처리한다.
        }
    }
}

위 코드는 작동은 하지만 난잡하고 직관성이 떨어진다.

어댑터 메서드를 활용한 스트림 반복

public class Item47 {
    public static void main(String[] args) {
        for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
            // 프로세스를 처리한다.
        }
    }

    public static <E> Iterable<E> iterableOf(Stream<E> stream) {
        return stream::iterator;
    }
}
  • 어댑터 메서드를 사용하면 자바의 타입 추론이 문맥을 잘 파악하여 어댑터 메서드 안에서 따로 형변환하지 않아도 된다.
  • 어댑터를 사용하면 어떤 스트림도 for-each 문으로 반복할 수 있다.

예제 2 : Iterable → Stream adaptor

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



-> 객체 시퀀스를 반환하는 메서드를 작성할 때 오직 스트림 파이프라인에서만 쓰일 걸 안다면 스트림을 반환하고, 반환된 객체들이  반복문에서만 쓰일 걸 안다면 Iterable을 반환하자. 하지만 공개 API에서는 2개다 지원하는게 좋다.

 

■ Collection VS Stream

Collection

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

Stream

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

 

 

 

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

 

PowerSet은, 각 원소의 인덱스를 비트 필드로 이용하는 아이디어다.

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이나 Iterable을 리턴하는 API에는 Stream <-> Iterable로 변환할 수 있도록 어댑터 메서드가 필요하다.
  • 어댑터는 클라이언트 코드를 어수선하게 만들고 더 느리다.
  • 원소 시퀀스를 반환하는 메서드를 작성할 때는 Stream, Iterator를 모두 지원할 수 있게 작성하라
  • 컬렉션을 반환할 수 있다면 컬렉션을 반환하도록 한다.
  • 반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하도록 한다.
    • 만약 그렇지 않다면 전용 컬렉션을 구현할지 고민해보도록 한다.
 
 

참고: https://insight-bgh.tistory.com/442  

        https://velog.io/@banjjoknim/%EC%95%84%EC%9D%B4%ED%85%9C-47.-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C%EB%8A%94-%EC%8A%A4%ED%8A%B8%EB%A6%BC%EB%B3%B4%EB%8B%A4-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%B4-%EB%82%AB%EB%8B%A4

   https://daebalprime.tistory.com/entry/Effective-Java-3E-ITEM-47-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C%EB%8A%94-%EC%8A%A4%ED%8A%B8%EB%A6%BC%EB%B3%B4%EB%8B%A4-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%B4-%EB%82%AB%EB%8B%A4