2016-04-25 77 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