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

2021-06-20

일련의 원소를 반환하는 메서드는 자바 7까지 반환 타입으로 Collection , Set , List 같은 컬렉션 인터페이스나, Iterable 이나 배열 을 사용 했다.

그런데 자바 8에서 스트림이라는 개념이 들어오게 되었는데, 스트림은 반복(iteration)을 지원하지 않는다.

따라서 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다.

만약 API를 스트림만 반환하도록 짜놓으면 반한된 스트림을 for-each로 반복하길 원하는 사용자는 불만이 생기게 된다.

하지만 Stream 인터페이스는 Iterable 인터페이스가 정의한 추상 메서드를 전부 포함할 뿐만 아니라, Iterable 인터페이스가 정의한 방식대로 동작하는데, 그럼에도 불구하고 for-each로 스트림을 반복할 수 없는 이유는 Iterable을 확장하지 않아서다.

Stream에서 iterator 사용하기

Stream의 iterator 사용

public static void main(String[] args) {

     List<Object> testList = List.of();

    for (Object obj: (Iterable<Object>)testList.stream()::iterator) {

    }
}

위에 코드는 타입추론의 한계로 개발자가 형변환을 명시적으로 해줘야한다.

어댑터 메서드 사용

public class Main {
    public static void main(String[] args) {

         List<Object> testList = List.of();
        for (Object obj: iterableOf(testList.stream())) {

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

어댑터 메서드를 사용하면 타입추론이 문맥을 잘 파악하여 형변환을 따로하지 안해도 된다.

iterator에서 Stream 사용하기

반대로 API가 Iterable만 반환하면 스트림을 사용하려는 사용자는 불만이 생기게 된다.

하지만 어댑터 메서드를 구현해 손쉽게 구현할 수 있다.

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

그러므로 Collection을 반환하자

  • Collection 인터페이스는 Iterable 하위타입이다.
  • Collection 인터페이스는 stream 메서드도 제공한다.

그러므로 Collection을 반환하는 것이 가장 좋다.

정리

  • 원소 시퀀스를 반환하는 메서드를 작성할 때, 스트림으로 처리하길 원하는 사용자와 반복으로 처리하길 원하는 사용자 둘다 만족 시키려고 노력하자.
  • 반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 봔한하자.
    • 그렇지 않으면 전용 컬렉션을 구현할지 고민하자
  • 컬렉션을 반환하는게 불가능하면 스트림과 Iterable 중 더 자연스러운 것을 반환하자.