2 minute read

스트림은 기본적으로 iteration을 지원하지 않으므로, Stream과 반복문을 적절하게 조합해야 좋은 코드라고 할 수 있다.

사실 Stream 인터페이스는 Iterable 인터페이스가 정의한 추상 메서드를 모두 포함하기는 하나… Stream 인터페이스가 Iterable을 확장(extend)하지 않기 때문에 iteration이 불가하다.

어댑터 메서드를 사용한 변환

Stream 내부에 구현된 () iterator 메서드에 의해, 메서드 참조를 건네 해결할 수 있을 것 같지만 실제로는 명시적 형변환이 필요하며 직관적이지 않다.

// 
for(ProcessHandle ph : (Iterable<ProcessHandle>) ProcessHandle.allProcesses().iterator()) { 
...
}

iterator보다는 어댑터 메서드인 iterableOf() 를 구현해 사용하는 것이 좋다. (자바의 타입 추론에 의해 형변환된다.)

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

for (ProcessHandle ph : iterableOf(ProcessHandle.allProcesses()) {  
...
}

반대로 IterableStream으로 변환하고자 할 때에도, 어댑터 메서드인 streamOf()를 구현하여 사용하는 것이 좋다.

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

이러한 스트림 혹은 반복자를 구현하여야 할 때, 필요한 메서드의 여부를 고려하여 Stream 혹은 Iterator를 구현하자.

Collection

공개 API의 경우에는 Stream, Iterator 두가지 모두를 고려해야 하며, Collection은 이 두가지를 모두 사용할 수 있으므로 되도록 Collection이나 이의 하위 타입으로 구현하자.

public interface Collection<E> extends Iterable<E> {
// 컬렉션은 Iterable를 확장한다.
...
}

위에서 확인할 수 있듯 CollectionIterable, Stream 을 모두 지원할 수 있다.

AbstractCollection을 활용하는 전용 컬렉션

그러나 컬렉션을 반환한다는 이유 하나만으로 덩치가 큰 시퀀스를 메모리에 바로 올려서는 안된다. 이렇게 반환할 시퀀스의 메모리가 크지만 표현이 간결한 경우, 전용 컬렉션을 구현하는 것을 고려할 수 있다.

이와 같이 AbstractCollection을 활용해 전용 컬렉션을 구현하는 경우, Iterable 메서드와 contains(), size() 3가지 메서드만 구현하면 된다.

public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
        List<E> src = new ArrayList<>(s);

				// (1)
        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) // (2)
                    if ((index & 1) == 1)
                        result.add(src.get(i));
                return result;
            }
        };
    }
}

Stream 을 사용하는 것이 더 편한 경우

하지만 contains(), size() 메서드를 구현하는 것이 어려운 경우에는 그냥 Stream 혹은 Iterable를 사용하여도 무방하다.

public class SubList {  // 한 리스트의 부분 리스트
  
    public static <E> Stream<List<E>> of(List<E> list) {  
        return Stream.concat(Stream.of(Collections.emptyList()),   
                             prefixes(list).flatMap(SubList::suffixes));  
    }  
  
    public static <E> Stream<List<E>> prefixes(List<E> list) {  
        return IntStream.rangeClosed(1, list.size())  
                        .mapToObj(end -> list.subList(0, end));  
    }  
  
    public static <E> Stream<List<E>> suffixes(List<E> list) {  
        return IntStream.rangeClosed(0, list.size())  
                        .mapToObj(start -> list.subList(start, list.size()));  
    }  
}

위와 같이 반복이 시작되기 이전에 contains(), size()를 확정짓기 어려운 경우 Stream을 활용할 수 있다.

Leave a comment