2016-05-05 14 views
0

Ich habe zwei Map s, die die gleichen Schlüssel haben sollen (Annahme).Kombinieren Kartenwerte in Java 8 mit Streams

Ich brauche eine Aktion auf entsprechende Elemente

Java 7 Code auszuführen:

Map<X,Y> m1, m2; 

for (Map.Entry<X,Y> kvp: m1.entrySet()) { 
    K key = kvp.getKey(); 
    Y value1 = kvp.getValue(); 
    Y value2 = m2.get(key);  


    doSomething(value1, value2); 
} 

Mein Ziel einer Lambda für einen solchen Betrieb zu verwenden ist

So etwas wie

Map<X,Y> m1, m2; 

[initStreamHere](m1, m2).forEachCorrespondingEntry((k, v1, v2)-> doSomething(v1, v2)); 

Im Allgemeinen könnte es cool sein, wenn v1 oder v2 ist null oder nicht entsprechend dem Vorhandensein oder Nicht des entsprechenden Schlüssels.

Bietet Java eine solche coole Funktion? Oder ist es möglich, mit benutzerdefinierten Code in weniger als 50 Zeilen zu implementieren?

Mein Ziel ist es, einen Lambda-Ausdruck zu verwenden, die

+1

Nur 'forEach' über' m1', capture 'm2' mit' get' mit dem 'm1' Schlüssel durch die bereitgestellten 'forEach' und nenne dein' doSomething'. –

+0

Uhmmm ... Ich habe nur gefragt, ob Java sich um das Lookup auf m2 kümmern könnte und * serve * mir die Ergebnisse :-) Ich habe den Code schon die alte Java 7 Mode implementiert. PS es ist nutzlos, auf m1 zu streamen und auf m2 zu kommen, ich hätte meinem Chef ein eindrucksvolles Beispiel gezeigt sonst –

+0

Erstelle eine 'MultiMap' aus den zwei Karten und streame diese Ergebnisse. Oder erstellen Sie Ihre eigene 'Map' mit zwei Werten und streamen Sie diese (so müssen Sie sich nicht mit einem' Collection'- oder 'List'-Wert befassen). –

Antwort

1

Solange es nur kuriose Frage ist. Für die Produktion/Ihre Kollegen ist es besser etwas viel verständlicher zu schreiben.

interface EntryConsumer<K, V> { 
    void apply(K key, V v1, V v2); 
} 

public static <K, V> void fancyStreams(Map<K, V> m1, Map<K, V> m2, EntryConsumer<K, V> consumer) { 
    Stream 
     .concat(m1.entrySet().stream(), m2.entrySet().stream()) 
     .filter(e -> e.getValue() != null) // filter out nulls 
     .collect(Collectors.groupingBy(Map.Entry::getKey)) 
     .entrySet() 
     .stream() 
     .filter(e -> e.getValue().size() == 2) // filter out non-matching key-values 
     .forEach(e -> consumer.apply(e.getKey(), e.getValue().get(0).getValue(), e.getValue().get(1).getValue())); 
} 

public static <K, V> void plainOldIteration(Map<K, V> m1, Map<K, V> m2, EntryConsumer<K, V> consumer) { 
    m1.entrySet() 
     .forEach(e -> { 
      if (m2.containsKey(e.getKey())) 
       consumer.apply(e.getKey(), e.getValue(), m2.get(e.getKey())) 
     }); 
} 

// Actual usage 
Map<String, Integer> m1, m2; 
m1 = Maps.of("1", 22, "2", 23); 
m2 = Maps.of("1", 20, "2", 19, "3", 21); 
fancyStreams(m1, m2, (k, v1, v2) -> System.out.println(k + ": " + (v1 + v2))); 
plainOldIteration(m1, m2, (k, v1, v2) -> System.out.println(k + ": " + (v1 + v2))); 

Und Ihre tatsächliche Anfrage (etwas ausführliche)

public class EntryStream<K, V> { 

    private final Map<K, V> m1; 
    private final Map<K, V> m2; 

    private EntryStream(Map<K, V> m1, Map<K, V> m2) { 
     this.m1 = m1; 
     this.m2 = m2; 
    } 

    public static <K, V> EntryStream<K, V> of(Map<K, V> m1, Map<K, V> m2) { 
     return new EntryStream<>(m1, m2); 
    } 

    public void forEachCorrespondingEntry(EntryConsumer<K, V> consumer) { 
     m1.entrySet() 
      .forEach(e -> { 
       if (m2.containsKey(e.getKey())) consumer.apply(e.getKey(), e.getValue(), m2.get(e.getKey())); 
      }); 
    } 
} 

// Usage 
EntryStream.of(m1, m2).forEachCorrespondingEntry((k, v1, v2) -> System.out.println(k + ": " + (v1 + v2))); 
+0

Unglaublich nah. Ich muss nicht die ganze Logik mit Lambdas implementieren, ich brauche die "Schnittstelle" in Lambdas. Solange ich 'forEachCorrespondingEntry ((k, v1, v2) ->' schreiben kann, ist es großartig. Wenn die Backing-Implementierung Stream oder normale alte Loops verwendet, ist es in Ordnung –

+0

Warum EntryStream nicht genau das ist, was Sie brauchen?Übrigens, es ist leicht, auf mehr als zwei Karten zu verallgemeinern. – qwwdfsad

+0

Es tut mir leid, dass ich über den Kettenaufruf auf Ihrem ersten Fragment sprach. –

-1

So etwas wie dies den Schlüssel und die beiden entsprechenden Werte annimmt?

m1.entrySet() 
    .stream() 
    .filter(kvp -> m2.containsKey(kvp.getKey())) 
    .forEach(kvp -> doSomething(kvp.getValue(), m2.get(kvp.getKey()))); 
+0

Nein Entschuldigung, ich wollte ein Lambda haben, das alle 3 nimmt. Ich frage die ganze Frage nach Neugier und Spaß, nicht weil ich in etwas stecken bin :-) –

0

Hier schrieb ich schönes Beispiel dafür, wie Sie Merge-Funktion für Ihre Zwecke

public class MapCombine { 

public static Map<String,Integer> combineMaps(Map<String,Integer>map1,Map<String,Integer>map2){ 
    map2.forEach((key,value)->{ 
     map1.merge(key, map1.get(key), MapCombine::combine); 
    }); 
    return map1; 
} 

public static int combine(Integer a , Integer b) { 
    return a+b; 
} 

public static void main (String ...args) { 
    Map<String,Integer>map1 = new HashMap<>(); 
    Map<String,Integer>map2 = new HashMap<>(); 
    map1.put("Red", 1); 
    map1.put("Blue", 12); 
    map1.put("Black", 11); 
    map2.put("Red", 1); 
    map2.put("Blue", 111);  

    // This will print total for each color 
    System.out.println(combineMaps(map1, map2)); 

} 

}

Hier ist die Ausgabe {Rot = 2, Blau = 24, Schwarz = 11 verwenden können }