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);
}


  • 첫 번째 인자인 SupplierArrayList::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로 안전하게 형 변환할 수 있다.


+ Recent posts