2017-01-16 1 views
3

Der folgende Code funktioniert und ist lesbar, aber es scheint mir, ich habe Zwischenoperationen, die das Gefühl haben, sie sollten nicht notwendig sein. Ich habe diese vereinfachte Version geschrieben, da der eigentliche Code Teil eines viel größeren Prozesses ist.Java 8 Stream Collectors - Collector zum Erstellen einer Map mit Objekten in mehreren Buckets

Ich habe eine Collection von Widget, jede mit einem Namen und mehreren Typen (angezeigt durch Konstanten der WidgetType enum). Diese verschiedenen Typen sind als Stream<WidgetType> abrufbar, aber wenn nötig, könnte ich diese als einen anderen Typ zurückgeben. (Aus verschiedenen Gründen ist es stark wünschenswert, dass diese als Stream<WidgetType> zurückgeschickt werden, weil, wie diese Widgets verwendet werden, später in dem eigentlichen Code.)

Diese Widgets werden zu einem EnumMap<WidgetType, List<Widget>> welche später übersetzt in ein EnumMap<WidgetType, Widget[]>.

Wenn jeder Widget nur eine einzige WidgetType hatte, dies wäre eine triviale lösen, aber, da jedes Widget 1 oder mehr Typen haben könnte, bin ich ganz über mich mit der Syntax des Collectors.groupingBy() Methode (und seine Überlastungen) Auslösung.

Hier ist das Codebeispiel, wieder voll funktionsfähig und gibt mir das genaue Ergebnis, das ich brauche.

class StackOverFlowExample { 

    private final Map<WidgetType, Widget[]> widgetMap = new EnumMap<>(WidgetType.class); 

    public static void main(String[] args) { new StackOverFlowExample(); } 

    StackOverFlowExample() { 
     Collection<Widget> widgetList = getWidgetsFromWhereverWidgetsComeFrom(); 

     { 
      final Map<WidgetType, List<Widget>> intermediateMap = new EnumMap<>(WidgetType.class); 
      widgetList.forEach(w -> 
        w.getWidgetTypes().forEach(wt -> { 
         intermediateMap.putIfAbsent(wt, new ArrayList<>()); 
         intermediateMap.get(wt).add(w); 
        }) 
      ); 
      intermediateMap.entrySet().forEach(e -> widgetMap.put(e.getKey(), e.getValue().toArray(new Widget[0]))); 
     } 

     Arrays.stream(WidgetType.values()).forEach(wt -> System.out.println(wt + ": " + Arrays.toString(widgetMap.get(wt)))); 
    } 

    private Collection<Widget> getWidgetsFromWhereverWidgetsComeFrom() { 
     return Arrays.asList(
       new Widget("1st", WidgetType.TYPE_A, WidgetType.TYPE_B), 
       new Widget("2nd", WidgetType.TYPE_A, WidgetType.TYPE_C), 
       new Widget("3rd", WidgetType.TYPE_A, WidgetType.TYPE_D), 
       new Widget("4th", WidgetType.TYPE_C, WidgetType.TYPE_D) 
     ); 
    } 

} 

Diese Ausgänge:

TYPE_A: [1st, 2nd, 3rd] 
TYPE_B: [1st] 
TYPE_C: [2nd, 4th] 
TYPE_D: [3rd, 4th] 

Der Vollständigkeit halber ist hier die Widget Klasse und die WidgetType Enum:

class Widget { 

    private final String  name; 
    private final WidgetType[] widgetTypes; 

    Widget(String n, WidgetType ... wt) { name = n; widgetTypes = wt; } 

    public String getName() { return name; } 
    public Stream<WidgetType> getWidgetTypes() { return Arrays.stream(widgetTypes).distinct(); } 

    @Override public String toString() { return name; } 
} 

enum WidgetType { TYPE_A, TYPE_B, TYPE_C, TYPE_D } 

Irgendwelche Ideen auf einem besseren Weg, diese Logik sind willkommen auszuführen. Vielen Dank!

Antwort

4

IMHO, ist der Schlüssel, um eine Widget Instanz in eine Stream<Pair<WidgetType, Widget>> Instanz zu konvertieren. Sobald wir das haben, können wir flatMap einen Strom von Widgets und sammeln auf den resultierenden Strom. Natürlich haben wir Pair in Java nicht, also muss stattdessen AbstractMap.SimpleEntry verwendet werden.

widgets.stream() 
     // Convert a stream of widgets to a stream of (type, widget) 
     .flatMap(w -> w.getTypes().map(t->new AbstractMap.SimpleEntry<>(t, w))) 
     // Grouping by the key, and do additional mapping to get the widget 
     .collect(groupingBy(e->e.getKey(), 
       mapping(e->e.getValue, 
         collectingAndThen(toList(), l->l.toArray(new Widget[0]))))); 

P.S. Dies ist eine Gelegenheit, bei der der Vorschlag von IntelliJ kein Lambda mit Methodenreferenz verkürzt.

+3

Dies muss noch den Schritt von 'Liste ' auf 'Widget []'. Könnte 'collectingAndThen (toList(), l -> l.toArray (neues Widget [0]))' verwenden. –

+1

Danke, @JornVernee. Bearbeitet. –

+0

'Collectors.mapping()' ist der Teil, den ich vermisste. Die 'collectingAndThen()' Lösung ist auch ein Bonus. (Ich hatte mich ziemlich damit abgefunden, dass ich diesen zusätzlichen Schritt brauchte.) Es wurde genickt, Luke. +1 den ganzen Tag. Tausend Dank! :) – Darkly

1

Dies ist ein bisschen verschachtelt, aber es produziert die gleiche Ausgabe, nicht unbedingt in der gleichen Reihenfolge. Es verwendet einen statischen Import von java.util.stream.Collectors.*.

widgetMap = widgetList.stream() 
     .flatMap(w -> w.getWidgetTypes().map(t -> new AbstractMap.SimpleEntry<>(t, w))) 
     .collect(groupingBy(Map.Entry::getKey, collectingAndThen(mapping(Map.Entry::getValue, toSet()), s -> s.stream().toArray(Widget[]::new)))); 

Ausgang auf meinem Rechner:

TYPE_A: [1st, 3rd, 2nd] 
TYPE_B: [1st] 
TYPE_C: [2nd, 4th] 
TYPE_D: [3rd, 4th] 
Verwandte Themen