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

2023. 5. 31. 10:44Java/Effective Java

서론

  • 자바 7 까지는 일련의 원소를 반환하는 메서드의 반환 티입으로 컬렉션 인터페이스나 Iterable, 배열을 써왔다.
    • 기본은 컬렉션 인터페이스이며, for-each 문에서만 쓰이거나 반환된 원소 시퀀스가 일부 Collection 메서드를 구현할 수 없을 때(주로 contains(Object) 같은)는 Iterable 인터페이스를 썼다.
    • 반환 원소들이 기본 타입이거나 성능에 민감한 상황이라면 배열을 썼다.
  • 자바 8의 스트림 도입 후 선택이 복잡해졌다.

 

 

Stream

  • Iterable 인터페이스가 정의한 추상 메서드를 전부 포함할 뿐만 아니라, Iterable 인터페이스가 정의한 방식대로 동작한다.
  • 하지만 for-each 로 스트림을 반복할 수 없는 이유는 Iterable 을 확장(extends)하지 않아서이다.
public interface Stream<T> extends BaseStream<T, Stream<T>> {
	...
}

public interface BaseStream<T, S extends BaseStream<T, S>>
        extends AutoCloseable {
	...
}

 

 

Collection

  • Iterable 하위 타입이며 stream 메서드 제공
  • 반복과 스트림을 동시에 지원
public interface Collection<E> extends Iterable<E> {
	...

	// Stream 메서드 제공
	default Stream<E> stream() {
      return StreamSupport.stream(spliterator(), false);
  }

	default Stream<E> parallelStream() {
      return StreamSupport.stream(spliterator(), true);
  }
}

 

 

스트림을 반복하기 위한 우회

  • Stream의 iterator 메서드에 메서드 참조를 건네면?
  • 컴파일 오류남
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {	
}

 

  • Iterable로 형변환
  • 작동은 하지만 난잡하고 직관성이 떨어짐
for (ProcessHandle ph : (Iterable<ProcessHandle>) ProcessHandle.allProcesses()::iterator) {
}

 

  • 어댑터 메서드 제공을 통해 보안
  • 이 경우, 자바의 타입 추론이 문맥을 잘 파악하여 어댑터 메서드 안에서 따로 형변환 하지 않아도 된다.
@Test
public void test() {
	for (ProcessHandle ph : iterableOf(ProcessHandle.allProcesses())) {
	}
}
	
// Stream<E>를 Iterable<E>로 중개해주는 어댑터
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
	return stream::iterator;
}

 

  • 반대의 경우의 어뎁터
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
	return StreamSupport.stream(iterable.spliterator(), false);
}

 

 

정리

  • 원소 시퀀스를 반환하는 메서드를 작성할 때 Iterable 과 Stream 두 방식 모두 작성하자
  • 하지만 가능하면 Iterable 하위타입이면서 Stream을 지원하는 Collection 의 하위 타입을 반환하도록 하자.
  • 반환할 컬렉션의 원소 개수가 적다면 ArrayList와 같은 표준 컬렉션 구현체를 반환하라
    • 시퀀스 크기가 크다면 전용 컬렉션을 구현하는 것을 고민하라