2017-05-17 13 views
3

の異なる種類に収集だから私は「作品は」(簡単にするためにいくつかの名前を置き換える)ことを、このコードを持っていますSomeClassAフィールドでグループ化した後、内部コレクションはSomeClassBになります。例えば、クラスは次のように見れば:ネストされた地図にgroupingByを使用して、そのオブジェクト

彼らは両方のすべての引数のコンストラクタ

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

class SomeClassB { 
    T someData; 
    String someId; 
} 

を持っており、どこかの方法がありますと仮定:

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 
} 

私はの結果のリストを平らにしたいですいくつかのクラスを

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 e。 SomeClassBに基づくリストの束をSomeClassAのすべての値の単一のリストに集めるにはどうすればよいですか? 1つのClassAが1つのClassBにマッピングされている場合、Collectors.mappingを使用して動作させる方法を知っていますが、各ClassAが複数のClassBを生成するため、どのように動作させるかわかりません。

すべてのアイデアをいただければ幸いです。ありがとう!

そのようなカスタムコレクターで
+6

あなたはありますJavaの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

:我々はすべてのおかげで、リンクのため@shmoselするためにJava 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

:あなたは後にしているものを達成することができます

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に蓄積されます。

このストリームのストリームが作成された直後は、ストリームを連結したいだけなので、ストリームはアイデンティティ関数でフラット・マップされます。 (私はストリームのストリームの代わりにストリームのリストを使用することができたかもしれないが、私は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())))); 

EDIT: @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'コレクタは、アキュムレータ関数で平坦化の権利を実行することで実装できます。 [この回答の終わり(http://stackoverflow.com/a/39131049/2711488)はそのような実装です。それだけでなく、より効率的ですが、より簡単でも、閉鎖のポリシーを気にしています。これは、Java 9の実装と同等(同一でない場合)です。 – Holger

+0

@Holgerあなたのバージョンでは、フラット・マッピングは、ストリームが終了したときに(その要素をダウンストリーム・コレクターに蓄積することによって)暗黙的に実行されます。あなたは自分の答えにリンクしてフラット・マッピング・コレクターをそのままコピーして、コレクターがあなたのものであることを読者に明瞭に伝えることを許可しますか? –

関連する問題