[Effective Java] 7 - item47 반환 타입으로는 스트림보다 컬렉션이 낫다.
스트림은 기본적으로 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()) {
...
}
반대로 Iterable
를 Stream
으로 변환하고자 할 때에도, 어댑터 메서드인 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를 확장한다.
...
}
위에서 확인할 수 있듯 Collection
은 Iterable
, 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