Java

Stream(스트림) - 7. Collector 구현 (최종연산 3/3)

jamie. 2020. 3. 11. 22:14
반응형

Collector 작성

 Collector인터페이스를 구현

Collector Interface

 직접 구현해야 하는 5개의 메서드

public interface Collector<T, A, R> {
    Supplier<A>       supplier();
    BiConsumer<A, T>  accumulator();
    BinaryOperator<A> combiner();
    Function<A, R>    finisher();
    
    Set<Characteristics> characteristics(); // 컬렉터의 특성이 담긴 Set 반환
       ...
}

 characteristics()를 제외하면 모두 반환타입이 함수형 인터페이스 = 4개의 람다식

supplier()    작업 결과를 저장할 공간 제공
accumulator() 스트림의 요소를 수집(collect)할 방법 제공
              스트림 요소를 어떻게 supplier가 제공한 공간에 누적할지 정의
combineder()  두 저장공간을 병합할 방법을 제공(병렬 스트림)
              병렬 스트림인 경우, 여러 쓰레드에 의해 처리된 결과를 어떻게 합칠지 정의
finisher()    결과를 최종적으로 변환할 방법을 제공
              변환이 필요없다면 항등 함수인 Function.identity()를 반환하면 됨
              
public Function finisher() {
    return Function.identity(); // 항등 함수 반환. return x -> x;와 동일
}

 characteristics()는 컬렉터가 수행하는 작업의 속성에 대한 정보를 제공하기 위한 것

 아래 세가지 속성 중 해당하는 것을 Set에 담아서 반환하도록 구현하면 됨

Characteristics.CONCURRENT      병렬로 처리할 수 있는 작업
Characteristics.UNORDERED       스트림의 요소의 순서가 유지될 필요가 없는 작업
Characteristics.IDENTITY_FINISH finisher()가 항등 함수인 작업
public Set<Characteristics> charateristics() {
    return Collections.unmodifiableSet(EnumSet.of(
                 Collector.Characteristics.CONCURRENT,
                 Collector.Characteristics.UNORDERED
           ));
}

 아무런 속성도 지정하고 싶지 않은 경우

Set<Characteristics> characteristics() {
    return Collections.emptySet();  // 지정할 특성이 없는 경우 비어있는 Set 반환
}

reduce() VS collect()

 finisher()를 제외한 supplier(), accumulator(), combiner()는 리듀싱에서 배울 때 등장한 개념

- Collector도 내부적으로 처리하는 과정이 리듀싱과 같다는 의미

 IntStream의 count(), sum(), max(), min() 등이 reduce()로 구현되어 있었음

- collect()로도 count()등의 메서드로 같은 일을 할 수 있음

long count = studentStream.count();
long count = studentStream.collect(Collectors.counting());

 근본적으로 하는 일이 같음

 collect() : 그룹화와 분할, 집계 등에 유용하게 쓰이고 병렬화에 있어서 reduce()보다 collect()가 더 유리함

 reduce()에 대해 잘 이해한다면, Collector를 구현하는 것이 어렵지 않음

예제 - Stream<String>의 모든 문자열을 하나로 결합해서 String으로 반환하는 컬렉터

String 배열의 모든 문자열을 하나의 문자열로 합치려면?

String[] strArr = { "aaa", "bbb", "ccc" };
StringBuffer sb = new StringBuffer(); // supplier(), 저장할 공간 생성

for(String tmp : strArr) {
    sb.append(tmp);                   // accumulator(), sb에 요소를 저장
}

String result = sb.toString();        // finisher(), StringBuffer를 String으로 변환

Stream<String>의 모든 문자열을 하나로 결합해서 String으로 반환하는 ConcatCollector

import java.util.Collections;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

public class ConcatCollector implements Collector<String, StringBuilder, String> {

    @Override
    public Supplier<StringBuilder> supplier() {
        return StringBuilder::new;
        // return () -> new StringBuilder();
    }

    @Override
    public BiConsumer<StringBuilder, String> accumulator() {
        return StringBuilder::append;
        // return (sb, s) -> sb.append(s);
    }

    @Override
    public BinaryOperator<StringBuilder> combiner() {
        return StringBuilder::append;
        // return (sb, sb2) -> sb.append(sb2);
    }

    @Override
    public Function<StringBuilder, String> finisher() {
        return StringBuilder::toString;
        // return sb -> sb.toString();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
}

만든 Collector를 사용하는 클래스

import java.util.Arrays;
import java.util.stream.Stream;

public class CollectorUse {

    public static void main(String[] args) {
        String[] strings = { "Jamie", "JAMIE", "jamie" };
        Stream<String> stringStream = Stream.of(strings);

        String result = stringStream.collect(new ConcatCollector());

        System.out.println(Arrays.toString(strings));
        System.out.println("result = " + result);
    }

}

[Jamie, JAMIE, jamie]
result = JamieJAMIEjamie

 

반응형