2016-04-25 69 views
4

我需要創建具有name1/2求和值value1/2的鍵的地圖。Java 8流減少和組合列表項到地圖

什麼可能是最簡潔的方式來重寫這個使用java 8流?

class Item { 

    private String name1; 
    private Integer value1; 
    private String name2; 
    private Integer value2; 

    public Item(final String name1, final Integer value1, final String name2, final Integer value2) { 
     this.name1 = name1; 
     this.value1 = value1; 
     this.name2 = name2; 
     this.value2 = value2; 
    } 
    //getters and setters 
} 
List<Item> list = Lists.newArrayList(
       new Item("aaa", 1, "bbb", 2), 
       new Item("bbb", 5, "ccc", 3), 
       new Item("aaa", 8, "bbb", 7), 
       new Item("bbb", 2, "aaa", 5)); 

Map<String, Integer> map = Maps.newHashMap(); 

for (Item item : list) { 
    map.merge(item.name1, item.value1, Integer::sum); 
    map.merge(item.name2, item.value2, Integer::sum); 
} 

System.out.println(map);//{aaa=14, ccc=3, bbb=16} 

回答

7

一種可能的解決方案是將平面地圖的每個項目進入由兩個條目由流:每個條目將組成的名稱和相應的值的。然後,通過對具有相同關鍵字的值的值求和,將其收集到地圖中。由於沒有內置對來保存這兩個值,因此我們可以使用AbstractMap.SimpleEntry

Map<String, Integer> map = 
    list.stream() 
     .flatMap(i -> Stream.of(new AbstractMap.SimpleEntry<>(i.name1, i.value1), new AbstractMap.SimpleEntry<>(i.name2, i.value2))) 
     .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum)); 

或者,使用自定義collect調用可能會更簡單。累加器合併每個密鑰,就像您在for循環體中所擁有的一樣。棘手的部分是組合器,在這種情況下,通過遍歷第二個映射的條目並將它們合併到第一個映射中,將兩個映射合併在一起。

Map<String, Integer> map = 
     list.stream() 
      .collect(
       HashMap::new, 
       (m, i) -> { 
        m.merge(i.name1, i.value1, Integer::sum); 
        m.merge(i.name2, i.value2, Integer::sum); 
       }, 
       (m1, m2) -> m2.forEach((k, v) -> m1.merge(k, v, Integer::sum)) 
      ); 
+1

我只是在問好奇。你爲什麼使用'reduce'而不是'collect'?有什麼優勢嗎? – Flown

+1

@Flown不,它真的是一樣的。 'flatMap'調用並不是那麼簡單,因爲必須有一個元組持有者(所以可能更難理解)。 'reduce'只是一種替代解決方案,它更像'for'循環。雖然列表相當大,但不確定,但表現可能有所不同。 – Tunaki

+3

問題是針對你的第二種方法,我在Oracle教程中找到了答案。 'reduce'用於不可變的和'collect'可變的縮減。在順序執行中,兩個縮減都顯示相同的行爲。如果你想使用並行化的'Stream',最好使用'collect':'list.stream()。collect(HashMap :: new,(m,i) - > {m.merge(i.name1,i (k,v) - > m2.merge().value1,Integer :: sum); m.merge(i.name2,i.value2,Integer :: sum);},(m1,m2) - > m1.forEach (k,v,Integer :: sum)))' – Flown