2017-05-17 8 views
3

다른 유형의 수집 그래서 난 "작품"(편의상 몇 가지 이름을 대체하는) 것을이 코드를 SomeClassA 필드로 그룹화 한 후에 내부 컬렉션은 SomeClassB가됩니다. 예를 들어, 클래스는 다음과 같이 경우 :중첩 된지도에 groupingBy를 사용하지만, 객체

public static Collection<SomeClassB> getSomeClassBsFromSomeClassA(SomeClassA someA) { 
    List<Some List of Class B> listOfB = someMethod(someA); 
    return listOfB; // calls something using someClassA, gets a list of SomeClassB 
} 

내가의 결과 목록을 평평하게하려면 :

둘 모두 인수 생성자에게

class SomeClassA { 
    String someCriteriaA; 
    String someCriteriaB; 
    T someData; 
    String someId; 
} 

class SomeClassB { 
    T someData; 
    String someId; 
} 

이 어딘가하는 방법이있다 가정 SomeClass B에

Map<String, Map<String, ImmutableList<SomeClassB>>> someMap = 
    someListOfClassA.stream() 
    .filter(...) 
    . // not sure how to group by SomeClassA fields but result in a list of SomeClassB since one SomeClassA can result in multiple SomeClassB 

나는 이것이 코드 abov에 어떻게 들어 맞을지 모르겠다. 이자형. SomeClassB를 기반으로 한 목록을 어떻게 수집하여 SomeClassA의 모든 값에 대한 단일 목록으로 만들 수 있습니까? 단일 ClassA가 하나의 ClassB에 매핑되는 경우 Collectors.mapping을 사용하여 작동시키는 방법을 알고 있지만 각 ClassA는 여러 ClassB를 생성하므로 작동 방법을 모릅니다.

모든 아이디어를 얻을 수 있습니다. 감사! 그래서 같은 사용자 지정 수집기와

+6

당신이해야합니다 자바 9 ['Collectors.flatMapping()'(http://download.java.net/java/jdk9/ 그룹의 다운 스트림 결과를 플랫화할 수있는 docs/api/java/util/stream/Collectors.html # flatMapping-java.util.function.Function-java.util.stream.Collector-). – shmosel

답변

4

: 우리 모두가 자바 (9)의 Collectors.flatMapping를 기다리는 동안

Map<String, Map<String, List<SomeClassB>>> someMap = 
       someListOfClassA.stream() 
         .filter(...) 
         .collect(Collectors.groupingBy(SomeClassA::getSomeCriteriaA, 
           Collectors.groupingBy(SomeClassA::getSomeCriteriaB, 
             Collectors.mapping(a -> getSomeClassBsFromSomeClassA(a), 
               flatMapToImmutableList())))); 
3

이 (덕분에이 링크를 @shmosel하기 : 당신이 달성 할 수

private static Collector<Collection<SomeClassB>, ?, ImmutableList<SomeClassB>> 
     flatMapToImmutableList() { 
     return Collectors.collectingAndThen(Collectors.toList(), 
       listOfCollectionsOfB -> 
         listOfCollectionsOfB.stream() 
           .flatMap(Collection::stream) 
           .collect(GuavaCollectors.toImmutableList())); 
    } 

당신이 계신), 자신 만의 수집기를 작성하여 원하는 것을 얻을 수 있습니다.

public static <T, D, R> Collector<T, ?, R> flatMapping(
     Function<? super T, ? extends Stream<? extends D>> streamMapper, 
     Collector<? super D, ?, R> downstream) { 

    class Acc { 
     Stream.Builder<Stream<? extends D>> builder = Stream.builder(); 

     void add(T t) { 
      builder.accept(streamMapper.apply(t)); 
     } 

     Acc combine(Acc another) { 
      another.builder.build().forEach(builder); 
      return this; 
     } 

     R finish() { 
      return builder.build() 
        .flatMap(Function.identity()) // Here! 
        .collect(downstream); 
     } 
    } 
    return Collector.of(Acc::new, Acc::add, Acc::combine, Acc::finish); 
} 

이 도우미 메서드는 Collector.of 및 로컬 클래스 Acc을 사용하여 제공된 streamMapper 함수에 의해 반환 된 스트림을 누적합니다.이 함수는 원본 스트림의 요소를 인수로 사용합니다. 이러한 스트림은 Stream.Builder에 누적되어 수집기의 피니셔 기능이 적용될 때 작성됩니다.

이 스트림 스트림이 빌드 된 직후에는 스트림을 연결하기 만하기 때문에 ID 함수를 사용하여 플랫 맵핑됩니다. (스트림 스트림 대신 스트림 목록을 사용할 수는 있지만, Stream.Builder은 매우 효율적이고 많이 사용되지 않는다고 생각합니다.)

Acc도 주어진 Acc의 스트림을이 Acc의 스트림 빌더로 병합하는 결합 메소드를 구현합니다. 이 기능은 원본 스트림이 병렬 인 경우에만 사용됩니다.

다음은 귀하의 예제와 함께이 방법을 사용하는 방법은 다음과 같습니다

Map<String, Map<String, ImmutableList<SomeClassB>>> map = someListOfClassA.stream() 
    .filter(...) 
    .collect(
     Collectors.groupingBy(SomeClassA::getSomeCriteriaA, 
      Collectors.groupingBy(SomeClassA::getSomeCriteriaB, 
       flatMapping(
        a -> getSomeClassBsFromSomeClassA(a).stream(), 
        ImmutableList.toImmutableList())))); 

편집이 : @Holger으로 아래 코멘트에 표시, 축적 스트림 빌더로 버퍼링 데이터에 대한 필요가 없습니다. 대신에 평면 매핑 수집기는 누적 기 함수에서 병합 실행을 수행하여 구현할 수 있습니다.Here is @Holger's own implementation of such collector, 나는 그의 동의를 그대로 여기에 복사하고있어 어떤 :

public static <T, U, A, R> Collector<T, ?, R> flatMapping(
     Function<? super T, ? extends Stream<? extends U>> mapper, 
     Collector<? super U, A, R> downstream) { 

    BiConsumer<A, ? super U> acc = downstream.accumulator(); 
    return Collector.of(downstream.supplier(), 
      (a, t) -> { 
       try (Stream<? extends U> s = mapper.apply(t)) { 
        if (s != null) s.forEachOrdered(u -> acc.accept(a, u)); 
       } 
      }, 
      downstream.combiner(), downstream.finisher(), 
      downstream.characteristics().toArray(new Collector.Characteristics[0])); 
} 
+1

빌더로 데이터를 버퍼링 할 필요가 없습니다. FlatMapping 컬렉터는 accumulator 함수에서 평탄화 권한을 수행함으로써 구현 될 수 있습니다. [이 대답의 끝] (http://stackoverflow.com/a/39131049/2711488)은 그러한 구현입니다. 더 효율적인 것은 아니지만 더 간단하면서도 종결 방침을 염려합니다. Java 9의 구현과 동일하지는 않습니다. – Holger

+0

@Holger 여러분의 버전에서 플랫 맵핑은 스트림이 종료되면 (다운 스트림 콜렉터에 요소를 누적함으로써) 암묵적으로 수행됩니다. 내 답변을 링크하고 평평한 매핑 수집기를 그대로 복사하여 수집기가 귀하의 것임을 독자에게 분명히 알릴 수 있습니까? –