커스텀 컬렉터

지니🧸·2023년 4월 12일
0

Java

목록 보기
6/13

🎞️ 커스텀 컬렉터로 성능 개선

n 이하의 자연수를 소수와 비소수로 분류하기

public Map<Boolean, List<Integer>> partitionPrimes(int n) {
	return IntStream.rangeClosed(2, n)
    		.boxed() 
            .collect(partitioningBy(candidate -> isPrime(candidate));
}
  • .boxed() : returns a Stream consisting of the elements of this stream, each boxed to an Integer

🎞️ 소수로만 나누기

지금까지 발견된 소수 리스트에 접근해 이 값들로 나눠떨어지는지 확인해서 범위 좁히기

  • 컬렉터 수집과정에서 부분결과에 접근해야 함
public static boolean isPrime(List<Integer> primes, int candidate) {
	return primes.stream().noneMatch(i -> candidate % i == 0);
}
  • 대상 숫자의 제곱근보다 작은 소수만 사용하도록 코드 최적화 필요
    • 다음 소수가 대상의 루트보다 크면 소수로 나누는 검사 멈춰!
  • 스트림 API는 이런 기능을 제공하는 메서드가 없기 때문에 커스텀 필요

정렬된 리스트와 predicate을 인수로 받아 리스트의 첫 요소에서 시작해서 predicate을 만족하는 가장 긴 요소로 이루어진 리스트 반환하는 메서드 구현

public static boolean isPrime(List<Integer> primes, int candidate) {
	int candidateRoot = (int) Math.sqrt((double) candidate);
    return primes.stream()
    			.takeWhile(i -> i <= candidateRoot)
                .noneMatch(i -> candidate % i == 0);
}
  • noneMatch: 모든 요소들이 주어진 조건을 만족하지 않는지

🎞️ Collector 클래스 커스텀 구현

🎞️ 1. Collector 클래스 시그니처 정의

Collector 인터페이스는 public interface Collector<T, A, R>로 정의

  • T: 스트림 요소의 형식
  • A: 중간 결과를 누적하는 객체의 형식
  • R: collect 연산의 최종 결과 형식

예시)

public class PrimeNumbersCollector 
	implements Collector<Integer, Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>>

🎞️ 2. 리듀싱 연산 구현

Collector 인터페이스에서 선언된 다섯 메서드 구현

supplier 메서드는 누적자를 만드는 함수 반환

  • 누적자로 사용할 맵 만들기
    • true, false 키와 빈 리스트로 초기화
public Supplier<Map<Boolean, List<Integer>>> supplier() {
	return () -> new HashMap<Boolean, List<Integer>>() {{
    	put(true, new ArrayList<Integer>());
        put(false, new ArrayList<Integer>());
    }};
}

accumulator 메서드는 스트림의 요소를 어떻게 수집할지 결정

public BiConsumer<Map<Boolean, List<Integer>> accumulator() {
	return (Map<Boolean, List<Integer>> acc, Integer candidate) -> {
    	acc.get(isPrime(acc.get(true), candidate)) // isPrime의 결과에 따라 소수 리스트와 비소수 리스트 만들기
        	.add(candidate); // candidate를 알맞는 리스트에 추가
    }
}

🎞️ 3. 병렬 실행할 수 있는 컬렉터 만들기

병렬 수집 과정에서 두 부분 누적자를 합칠 수 있는 메서드 만들기

예시) 두번째 맵의 소수 리스트와 비소수 리스트의 모든 수를 첫번째 맵에 추가하기만 하면됨

public BinaryOperator<Map<Boolean, List<Integer>>> combiner() {
	return (Map<Boolean, List<Integer>> map1, Map<Boolean, List<Integer>> map2) -> {
    	map1.get(true).addAll(map2.get(true));
        map1.get(false).addAll(map2.get(false));
        return map1;
    };
}

🎞️ 4. finisher 메서드와 characteristics 메서드

accumulator의 형식은 컬렉터 결과 형식과 같으므로 변환 과정이 필요 없음

finisher 메서드는 항등 함수 identity를 반환하도록 구현

public Function<Map<Boolean, List<Integer>>, 
				Map<Boolean, List<Integer>> finisher() {
	return Function.identity();
}
  • Function.identity(): returns a Function that always returns its input argument
    • Function: functional interface

커스텀 컬렉터는 IDENTITY_FINISH이므로 다음과 같이 characteristics 메서드 구현

public Set<Characteristics> characteristics() {
	return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
}

+) CharacteristicsENUM CONSTANTS

  • IDENTITY_FINISH
  • UNORDERD: 컬렉션 연산이 요소 입력 순서를 유지하고자 하지 않음
  • CONCURRENT: 본 컬렉터는 병렬이다
    • 여러 스레드에서 같은 결과 컨테이너로 accumulator 메서드가 병렬적으로 호출되도 ok

참고:

profile
우당탕탕

0개의 댓글