2017-05-17 1 views
3

So habe ich diesen Code, dass „Werke“ (einige Namen der Einfachheit halber zu ersetzen):Mit groupingBy in eine verschachtelte Karte, aber das Sammeln auf eine andere Art von Objekt

Map<String, Map<String, ImmutableList<SomeClassA>>> someMap = 
     someListOfClassA.stream() 
     .filter(...) 
     .collect(Collectors.groupingBy(SomeClassA::someCriteriaA, 
      Collectors.groupingBy(SomeClassA::someCriteriaB, GuavaCollectors.toImmutableList() 
      ) 
    )); 

Allerdings mag ich diesen Code ändern so dass die innere Sammlung von SomeClassB nach dem Gruppieren von SomeClassA Feldern ist. Zum Beispiel aussehen, wenn die Klassen wie folgt aus:

vorausgesetzt, sie haben beide alle args Bauer

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

class SomeClassB { 
    T someData; 
    String someId; 
} 

Und es gibt eine Methode irgendwo:

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 
} 

Ich möchte die die resultierenden Listen glätten SomeClass Bs in

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 

Ich bin mir nicht sicher, wie das in den Code abov passen würde e. Wie kann ich eine Reihe von Listen basierend auf SomeClassB in einer einzigen Liste für alle Werte von SomeClassA sammeln? Wenn ein einzelnes ClassA einem einzelnen ClassB zugeordnet wird, weiß ich, wie es mit Collectors.mapping zum Laufen gebracht wird, aber da jedes ClassA zu mehreren ClassBs führt, bin ich mir nicht sicher, wie es funktioniert.

Irgendwelche Ideen würden geschätzt. Vielen Dank!

+6

In Java 9 Sie haben [ 'Collectors.flatMapping()'] (http://download.java.net/java/jdk9/ docs/api/java/util/stream/Collectors.html # flatMapping-java.util.function.Function-java.util.stream.Collector-), wodurch Sie die Ergebnisse unterhalb der Gruppierung reduzieren können. – shmosel

Antwort

4

Mit einem benutzerdefinierten Sammler wie so:

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

Sie erreichen können, was Sie nach:

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

Während wir alle für Java 9 Warten Collectors.flatMapping (danke für den Link @shmosel), können Sie Ihren eigenen Kollektor schreiben, um zu erzielen, was Sie wünschen:

Diese Helfermethode benutzt Collector.of und eine lokale Klasse Acc, um die von der bereitgestellten streamMapper-Funktion zurückgegebenen Datenströme zu akkumulieren, die ein Element des ursprünglichen Datenstroms als Argument verwenden. Diese Datenströme werden in einem Stream.Builder gespeichert, der erstellt wird, wenn die Kollektor-Finisher-Funktion angewendet wird.

Unmittelbar nachdem dieser Stream von Streams erstellt wurde, wird er mit der Identity-Funktion abgeglichen, da wir nur die Streams verketten wollen. (Ich hätte eine Liste von Streams anstelle eines Streams von Streams verwenden können, aber ich denke, dass Stream.Builder ist sowohl sehr effizient und stark unterbenutzt).

Acc implementiert auch eine Combiner-Methode, die den angegebenen Acc Stream der Ströme in diese Acc 's Stream Builder zusammenführen wird. Diese Funktionalität wird nur verwendet, wenn der ursprüngliche Stream parallel ist.

Hier ist, wie diese Methode mit Ihrem Beispiel zu verwenden:

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: Wie @Holger in den Kommentaren unten gibt es zum Puffern von Daten in einen Strom-Builder keine Notwendigkeit, wenn akkumulieren. Stattdessen kann ein Flat-Mapping-Kollektor implementiert werden, indem das Reduzierungsrecht in der Akkumulatorfunktion ausgeführt wird.Here is @Holger's own implementation of such collector, die ich kopieren hier wörtlich mit seiner Zustimmung:

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

Es ist nicht erforderlich, Daten in einem Builder zu puffern. Ein "flatMapping" -Kollektor kann implementiert werden, indem das Abflachungsrecht in der Akkumulatorfunktion ausgeführt wird. Am Ende von [diese Antwort] (http://stackoverflow.com/a/39131049/2711488) ist eine solche Implementierung. Es ist nicht nur effizienter, sondern auch einfacher und kümmert sich auch um die Abschlusspolitik. Es ist gleichwertig (wenn nicht identisch) mit der Implementierung von Java 9. – Holger

+0

@Holger Sie haben absolut Recht, in Ihrer Version wird das Flat-Mapping implizit ausgeführt, wenn der Stream beendet wird (indem seine Elemente im Downstream-Collector akkumuliert werden). Würdest du mir erlauben, mit deiner Antwort von mir zu verlinken und deinen Kartensammler wörtlich zu kopieren, um dem Leser klar zu machen, dass der Sammler dein ist? –

Verwandte Themen