스트림을 반복문으로 사용하는 방법에 대한 챕터이다.

 

자바 7 이전엔 일련의 원소를 반환하는 메소드의 반환타입으로

Collection, Set, List 와 같은 컬렉션 인터페이스나 Iterable, 배열을 썼다.

 

그러나 스트림이 나온 후 스트림이 Iterable을 지원하지 않으면서 선택의 문제가 생겼다.

스트림을 for-each로 사용하고 싶은 경우가 애매해진 것이다.

 

 

책에서 알려주는 극복 방법들을 봐보자

중개 어댑터 사용하기

 

 

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

 

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

 

for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) { 
    // 프로세스를 처리한다. 
}

Item 45에서 아나그램 프로그램을 만들었을때 스트림 버전과 반복버전을 기억해보자

스트림 버전은 Files.lines 메소드를 사용했고 반복버전은 스캐너를 이용했다.

 

둘 중 파일을 읽는 동안 발생하는 모든 예외를 알아서 처리해준다는 점에서 Files.lines쪽이 더 우수하다.

만약 프로그래머가 스트림만 반환하는 API를 for-each를 사용하고 싶다면 개인이 감수해야한다.

 

 

반대의 경우(Iterable만 반환가능)는 어댑터를 어떻게 해야하는지 보자

//Iterable<E>를 Stream<E>로 중개해주는 어댑터
public static <E> Stream<E> streamOf(Iterable<E> iterable) { 
    return StreamSupport.stream(iterable.spliterator(), false); 
}

 

 

객체 시퀀스를 반환하는 메소드를 작성하는데,

이 메소드가 오직 스트림 파이프라인에서만 쓰일 걸 안다면 마음 놓고 스트림을 반환하게 해주자.

 

 

반대로 반환된 객체들이 반복문에서만 쓰일 걸 안다면 Iterable을 반환하자.

하지만 공개 API에서는 두 상황을 모두 고려해주어야 한다.

 

 

  • Collection 인터페이스는 ITerable의 하위 타입이고 stream메소드도 제공한다.
  • 그렇기 때문에 원소 시퀀스를 반환하는 공개 API의 반환타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로는 최선이다.
  • 하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안된다.
  • 반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현하는 방법을 검토하자.

 

 

전용 컬렉션 구현

  • 주어진 집합의 멱집합(한 집합의 모든 부분집합을 원소로하는 집합)을 반환하는 상황이다.
  • {a,b,c}의 멱집합은 {}, {a}, {b},{c},{ab},{ac},{bc},{abc}다.
  • 원소의 개수가 n개면 멱집합의 원소 개수는 2^n개가 된다.
  • 그러니 멱집합을 표준 컬렉션 구현체에 저장하려는 생각은 위험하다.
  • AbstractList를 사용하자
  • 비결은 원소의 인덱스를 비트 벡터로 사용하는 것이다.
    • {a,b,c}의 멱집합은 원소가 8개이므로 사용하는 인덱스는 0~7이다.
    • 이진수로 바꾸면 000~111이 된다.
    • 이진수의 각 n번째 자리값이 각 원소를 의미하게 한다.
    • 000번째 원소는 {}, 001은 {a}, 101은 {c,a}가 되는 식이다.
public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
       List<E> src = new ArrayList<>(s);
       if(src.size() > 30) {
           throw new IllegalArgumentException("집합에 원소가 너무 많습니다(최대 30개).: " + s);
       }

       return new AbstractList<Set<E>>() {
           @Override
           public int size() {
               return 1 << src.size();
           }

           @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;
           }
       };
    }
}
  • 예제처럼 AbstractCollection을 활용할땐 Iterator용 메소드 외 contains와 size만 추가로 더 구현해주면 된다.
  • 구현이 불가능할 경우에는 컬렉션보다는 스트림이나 Iterable을 반환하는 것이 낫다.

 

 

 

정리

  • 원소 시퀀스를 반환하는 메소드를 만들때는 Stream -> Iterable, Iterable -> Stream의 경우를 생각하자.
  • 원소 개수가 많다면 전용 컬렉션을 기억하자

+ Recent posts