
자바는 7까지 원소 시퀀스(일련의 원소)를 반환하는 메서드의 반환 타입으로 Collection, List, Set 등의 컬렉션 인터페이스나 Iterable 배열을 사용했다.
기본은 Collection 인터페이스다.
forEach에서만 쓰이거나, contains같은 일부 collection 메소드를 구현할 수 없을 때는 Iterable을 썼고, 기본 타입이거나 성능에 민감할 때는 배열을 사용했다.
그런데?
자바 8에서 스트림이 생기면서, 선택지가 복잡해졌다.
item 45에서 봤듯이 stream은 반복을 지원하지 않는다.
forEach같은 애들은 List, Set과 같은 Iterable구현체에 대해서만 사용이 가능하다.따라서 스트림과 반복을 적절히 섞어야 좋은 코드가 된다.
여기서 재미없는 사실 하나!
사실 Stream 인터페이스는 Iterable 인터페이스가 정의한 추상 메서드를 모두 포함할 뿐만 아니라, Iterable이 정의한 방식대로 동작한다.
(그러니까 즉 Stream은 forEach를 구현하고 있다. 그런데도 forEach를 못쓰는 이유는 단순히 스트림이 Iterable을 확장하지 않았기 때문.)
그럼 우짜라는건지?
몇가지 방법이 있다.
1. Stream 내에 존재하는 iterator()를 활용하고, 컴파일러가 타입을 알 수 있게 적절하게 형변환을 해주기
Stream<String> mymind = Stream.of("네이버", "코테", "개망");
for (String mind : (Iterable<String>) mymind::iterator) { // 형변환
System.out.println(mind);
}
/*
네이버
코테
개망
*/
- 이 코드는 실전에서 사용하기에는 난잡하고 직관성이 떨어진다.
2. Stream<E>를 Iterable<E>로 중개해주는 어댑터를 만든다.
public static void main(String[] args) {
Stream<String> mymind = Stream.of("네이버", "코테", "개망");
for (String mind : iterableOf(mymind)) { // 어댑터 메소드 사용
System.out.println(mind);
}
}
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
return stream::iterator;
}
(출력은 같음)
만약 메서드가 Stream 파이프라인에서만 쓰인다면 Stream을 반환하자.
만약 반환된 객체들이 반복문에서만 쓰인다면 Iterable을 반환하자.
원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입 인터페이스를 사용하자.
왜?
Collection은 Iterable의 하위 타입인 동시에 Stream을 반환하는 메소드 stream()를 제공하기 때문이다.
(단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올리면 안돼!)
그러나, 반환된 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현해봐라.
전용 컬렉션
import java.util.*;
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() {
// 멱집합의 크기는 2를 원래 집합의 원소 수만큼 거듭제곱 것과 같다.
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;
}
};
}
}
위 예시는 멱집합을 전용 컬렉션으로 직접 구현한 코드이다.
(멱집합이란? 한 집합의 모든 부분집합을 의미. 즉, 원소 갯수가 n이라면 2^n이 멱집합의 원소의 개수)
딱봐도 얘네를 메모리에 올린다고 한다면 많은 메모리가 소모될 것이다.
따라서 위와 같은 멱집합을 구현하는 전용 컬렉션을 직접 구현하여 사용할 수 있다.
위 예시의 PowerSet은 각 원소의 인덱스를 비트 백터로 사용하므로 메모리 공간을 효율적으로 사용할 수 있다.
+ AbstractCollection을 활용할 때는 Iterable용 메서드 외에 contains와 size만 구현하면 된다.
핵심 정리
1. 원소 시퀀스를 반환하는 메서드를 작성할 때는 (스트림으로 처리하고자 하는 사용자 / 반복으로 처리하고자 하는 사용자) 모두를 만족시키려 노력해라.
2. 컬렉션을 반환할 수 있으면 반환하고, 이미 원소가 컬렉션에 담겨있거나 원소 개수가 적다면 표준 컬렉션(ArrayList)에 담아 반환해라.
3. 그것도 아니면 전용 컬렉션을 구현해봐라.
4. 컬렉션을 반환할 수 없으면 스트림과 Iterable중 더 자연스러운 것을 반환해라.
'JAVA > Effective Java' 카테고리의 다른 글
item 49. 매개변수가 유효한지 검사하라 (0) | 2022.07.07 |
---|---|
item 48. 스트림 병렬화는 주의해서 적용해라 (0) | 2022.07.02 |
item 46. 스트림에서는 부작용 없는 함수를 사용하라 (0) | 2022.07.02 |
item 45. 스트림은 주의해서 사용하라. (0) | 2022.06.18 |
item 44. 표준 함수형 인터페이스를 사용하라 (0) | 2022.06.18 |
댓글