JAVA

[이펙티브 자바] 아이템 47

수염차 2025. 5. 6. 21:17

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

 

- 스트림은 반복을 지원하지 않기 때문에 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다.

ex. foreach로 반복 불가능 (컴파일 오류 발생)

static Stream<ProcessHandle> allProcesses()

// Stream.iterator 사용
for(ProcessHandle p: ProcessHandle.allProcesses.iterator()) {

}

Test.java:6: error: method reference not expected here
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {

 

### 어댑터(Adapter)

1) Stream -> Iterable

Stream<E>  Iterable<E> 로 중개해주는 어댑터

public static <E> Iterable<E> iterableOf(Stream<E> stream) { 
        return stream::iterator;
}  
for(ProcessHandle p : iterableOf(ProcessHandle.allProcesses())){
}

어댑터를 사용한다면, 어떤 스트림도 for-each 문으로 반복할 수 있다.

참고로 자바의 타입 추론이 문맥을 잘 파악하기 때문에 어댑터 메서드 안에서 따로 형변환 하지 않아도 된다.

2) Iterable -> Stream

반대로 Iterable<E>  Stream<E> 로 변환해주는 어댑터도 다음과 같이 쉽게 구현 가능

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

 

 

 

### Collection 인터페이스

객체 시퀀스를 반환하는 메서드를 할 때, 스트림 파이프라인과 반복문 둘 중에 한 방식만 사용할 거라는 근거가 없으므로 어댑터를 쓰기 애매하다.

하지만 Collection 인터페이스는 Iterable의 하위 타입이고 stream 메서드도 제공하니 반복과 스트림을 동시에 지원한다. 따라서 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection 이나 그 하위 타입을 쓰도록 하자.

public interface Collection<E> extends Iterable<E> {
    ...
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

 

### 전용 컬렉션 구현

  1. 원소 사이즈가 작은 경우 : 표준 컬렉션 ( ArrayList나 HashSet )
  2. 원소 사이즈가 큰 경우 : 전용 컬렉션 구현

 

 

## 핵심정리

원소 시퀀스를 반환하는 메서드를 작성할 때는, 이를 스트림으로 처리하기를 원하는 사용자와 반복으로 처리하길 원하는 사용자가 모두 있을 수 있음을 떠올리고, 양쪽을 다 만족시키려 노력하자. 

컬렉션을 반환할 수 있다면 그렇게 하라. 

반환 전부터 이미 원소들을 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수
가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하라. 

그렇지 않으면 앞서의 멱집합 예처럼 전용 컬렉션을 구현할지 고민하라. 

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

만약 나중에 Stream 인터페이스가 Iterable을 지원하도록 자바가 수정된다면, 그때는 안심하고 스트림을 반환하면 될 것
이다(스트림 처리와 반복 모두에 사용할 수 있으니).