Ich wurde von einem Kollegen von mir mit einem interessanten Problem vorgestellt und ich konnte keine ordentliche und schöne Java 8 Lösung finden. Das Problem ist durch eine Liste von POJOs zu streamen und sie dann in einer Karte basierend auf mehreren Eigenschaften sammeln - die Abbildung bewirkt, dass die POJO mehrere MaleJava 8 Streams: Map das gleiche Objekt mehrmals basierend auf verschiedenen Eigenschaften
Stellen Sie sich folgende POJO auftreten:
private static class Customer {
public String first;
public String last;
public Customer(String first, String last) {
this.first = first;
this.last = last;
}
public String toString() {
return "Customer(" + first + " " + last + ")";
}
}
Set it up als List<Customer>
:
// The list of customers
List<Customer> customers = Arrays.asList(
new Customer("Johnny", "Puma"),
new Customer("Super", "Mac"));
Alternative 1: eine Map
außerhalb des "Strom" (oder eher außerhalb forEach
) verwenden.
// Alt 1: not pretty since the resulting map is "outside" of
// the stream. If parallel streams are used it must be
// ConcurrentHashMap
Map<String, Customer> res1 = new HashMap<>();
customers.stream().forEach(c -> {
res1.put(c.first, c);
res1.put(c.last, c);
});
Alternative 2: Map-Einträge erstellen und streamen, dann flatMap
sie. IMO ist es ein bisschen zu ausführlich und nicht so leicht zu lesen.
// Alt 2: A bit verbose and "new AbstractMap.SimpleEntry" feels as
// a "hard" dependency to AbstractMap
Map<String, Customer> res2 =
customers.stream()
.map(p -> {
Map.Entry<String, Customer> firstEntry = new AbstractMap.SimpleEntry<>(p.first, p);
Map.Entry<String, Customer> lastEntry = new AbstractMap.SimpleEntry<>(p.last, p);
return Stream.of(firstEntry, lastEntry);
})
.flatMap(Function.identity())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
Alternative 3: Dies ist eine andere, die ich bisher mit dem „schönsten“ Code kam, aber es nutzt die drei argument Version von reduce
und der dritte Parameter ist ein wenig schmuddelig wie in dieser gefunden Frage: Purpose of third argument to 'reduce' function in Java 8 functional programming. Darüber hinaus scheint reduce
für dieses Problem nicht gut geeignet zu sein, da es mutiert und parallele Streams möglicherweise nicht mit dem folgenden Ansatz funktionieren.
// Alt 3: using reduce. Not so pretty
Map<String, Customer> res3 = customers.stream().reduce(
new HashMap<>(),
(m, p) -> {
m.put(p.first, p);
m.put(p.last, p);
return m;
}, (m1, m2) -> m2 /* <- NOT USED UNLESS PARALLEL */);
Wenn der obige Code wie folgt gedruckt wird:
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
Das Ergebnis wäre:
{Super-= Kunden (Super Mac), Johnny = Kunde (Johnny Puma) , Mac = Kunde (Super Mac), Puma = Kunde (Johnny Puma)}
{Super = Kunde (Super Mac), Johnny = Kunde (Johnny Puma), Mac = Kunde (Super Mac), Puma = Kunde (Johnny Puma)}
{ Super = Kunden (Super Mac), Johnny = Kunde (Johnny Puma), Mac = Kunden (Super Mac), Puma = Kunde (Johnny Puma)}
So, nun zu meiner Frage: Wie sollte ich, in einer Java 8 geordneten Mode, streamen durch die List<Customer>
und dann irgendwie sammeln sie als Map<String, Customer>
, wo Sie das ganze Ding als zwei Schlüssel (first
AND last
), dh die Customer
ist zweimal zugeordnet. Ich möchte keine Bibliotheken von Drittanbietern verwenden. Ich möchte keine Karte außerhalb des Streams wie in Alt 1 verwenden. Gibt es noch andere nette Alternativen?
Der vollständige Code kann found on hastebin für einfache Kopie-einfügen, um die ganze Sache zu laufen.
Es ist nicht nur das 'collect()' ist "der bevorzugte Weg"; Die Verwendung von 'reduce()' ist einfach falsch. Reduzieren ist für Werte; Die Verzerrungen, die die Alternative 3 durchläuft, verletzen die Spezifikation von 'reduce()'. Und wenn Sie später versuchen, diesen Stream parallel zu starten, erhalten Sie nur die falsche Antwort. Auf der anderen Seite ist die Verwendung von 'collect()', wie Misha zeigt, für diesen Fall ausgelegt und ist entweder sequentiell oder parallel sicher. –
@BrianGoetz Danke, Brian. Ich habe meine Antwort modifiziert, um noch kraftvoller zu sein, als du deinen Kommentar gepostet hast. – Misha