Java8에서 Collector
인터페이스를 직접 구현할 일은 많지 않습니다. 보통은 Collectors
클래스에서 미리 구현한 static 메서드를 사용합니다. Collectors
라는 이름을 통해서도 어떤 역할을 하는지 충분히 유추할 수 있습니다.
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 1, 4);
List<Integer> evens = list.stream()
.filter(i -> i % 2 == 0)
.collect(Collectors.toList());
Map<Integer, List<Integer>> grouping = list.stream()
.collect(Collectors.groupingBy(Integer::valueOf));
Integer sum = list.stream()
.collect(Collectors.reducing(0, Integer::sum));
List<String> titles = Arrays.asList("Apple", "Banana", "cherry", "lemon");
String collect = titles.stream().collect(Collectors.joining(", "));
}
Stream
의 최종 연산중에 하나인 collect()
는 두 가지 타입이 있는데 Collectors
를 이용할 경우 인자를 Collector
인터페이스를 받는 메서드를 사용합니다. Collector
인터페이스를 한번 확인해 보겠습니다. 제네릭 타입도 3개나 되고 구현 대상 메서드도 5개나 되는 것이 뭔가 복잡한 느낌을 줍니다. 하나씩 살펴보겠습니다.
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set characteristics();
}
supplier()
는 빈 결과로 이루어진Supplier
를 반환해야 합니다. collect 과정에서 비어(empty) 있는 누적자 인스턴스를 만드는 파라미터가 없는 함수입니다.accumulator()
은 리듀싱 연산을 수행할 함수를 반환해야 합니다.finisher()
는 스트림 탐색을 끝내고 누적자 객체를 최종 결과로 반환하면서 누적 과정을 끝낼 때 호출할 함수를 반환해야 합니다.combiner()
는 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지를 정의합니다.characteristics()
는 스트림을 병렬로 리듀스 할 것인지 한다면 어떤 최적화를 선택해야 할지 힌트를 제공합니다.
Collectors.toList()
의 소스를 확인해보는 것이 좋을 것 같습니다.
public static Collector> toList() {
return new CollectorImpl<>((Supplier>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; }, CH_ID);
}
- 첫 번째 인자인
Supplier
는ArrayList::new
로 생성자 레퍼런스를 전달했습니다.(단순히ArrayList
의 생성자만 전달하면Supplier
로 처리가 되지 않기 때문에 캐스팅 한 것을 확인할 수 있습니다.) - 두 번째 인자는
BiConsumer
로는List::add
가 사용되어 있습니다.Supplier
를 통해서 생성된 누적자 인스턴스에add()
메서드를 통해서 리듀싱 연산을 수행하게 됩니다. - 세 번째 인자는
BinaryOperator
로 병렬로 처리할 때 서로 다른 서브 파트를 어떻게 누적시킬지를 결정하게 되는데addAll()
을 통해서 리스트를 추가하게 됩니다. - 네 번째 인자인 CH_ID는 미리 정의된
EnumSet
의 이름으로Characteristics.IDENTITY_FINISH
가 지정되어 있습니다.
눈에 띄는 것은
finisher()
를 정의하지 않았는데 이럴 경우 supplier()
에서 생성한 객체를 그대로 반환하게 됩니다.Characteristics enum의 종류와 설명
- Characteristics.UNORDERED - 리듀싱 결과는 스트림 요소의 방문 순서나 누적 순서에 영향을 받지 않는다.
- Characteristics.CONCURRENT - 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있으며 이 컬렉터는 스트림의 병렬 리듀싱을 수행할 수 있다. 컬렉터의 플래그에 UNORDERED를 함께 설정하지 않았다면 데이터 소스가 정렬되어 있지 않은(즉, 집합처럼 요소의 순서가 문의한) 상황에서만 병렬 리듀싱을 수행할 수 있다.
- Characteristics.IDENTITY_FINISH - finisher 메서드가 반환하는 함수는 단순히 identity를 적용할 뿐이므로 이를 생략할 수 있다. 따라서 리듀싱 과정의 최종 결과로 누적자를 객체로 바로 사용할 수 있다. 또한 누적자 A를 결과로 R로 안전하게 형 변환할 수 있다.
'Dev > Java' 카테고리의 다른 글
ConcurrentHashMap은 Client lock이 안된다. (0) | 2017.06.15 |
---|---|
자바 메모리 누수 확인 (0) | 2017.04.14 |
멀티 스레드에서 synchronized가 필요한 경우 (0) | 2017.04.13 |
CompletableFuture에 관해서 (0) | 2017.02.27 |