将两个Map <String,Integer>与Java 8 Stream API合并

我有两个(或更多) Map<String, Integer>对象。 我想将它们与Java 8 Stream API进行合并,使得常用键的值应该是值的最大值。

 @Test public void test14() throws Exception { Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3); Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4); List<Map<String, Integer>> list = newArrayList(m1, m2); Map<String, Integer> mx = list.stream()... // TODO Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4); assertEquals(expected, mx); } 

我怎样才能使这个testing方法绿色?

我已经collectCollectors了一段时间没有任何成功。

ImmutableMapnewArrayList来自Google Guava。)

 @Test public void test14() throws Exception { Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3); Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4); Map<String, Integer> mx = Stream.of(m1, m2) .map(Map::entrySet) // converts each map into an entry set .flatMap(Collection::stream) // converts each set into an entry stream, then // "concatenates" it in place of the original set .collect( Collectors.toMap( // collects into a map Map.Entry::getKey, // where each entry is based Map.Entry::getValue, // on the entries in the stream Integer::max // such that if a value already exist for // a given key, the max of the old // and new value is taken ) ) ; /* Use the following if you want to create the map with parallel streams Map<String, Integer> mx = Stream.of(m1, m2) .parallel() .map(Map::entrySet) // converts each map into an entry set .flatMap(Collection::stream) // converts each set into an entry stream, then // "concatenates" it in place of the original set .collect( Collectors.toConcurrentMap( // collects into a map Map.Entry::getKey, // where each entry is based Map.Entry::getValue, // on the entries in the stream Integer::max // such that if a value already exist for // a given key, the max of the old // and new value is taken ) ) ; */ Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4); assertEquals(expected, mx); } 
 Map<String, Integer> mx = new HashMap<>(m1); m2.forEach((k, v) -> mx.merge(k, v, Integer::max)); 
 mx = list.stream().collect(HashMap::new, (a, b) -> b.forEach((k, v) -> a.merge(k, v, Integer::max)), Map::putAll); 

这涵盖了任何大小的列表的一般情况,应该处理任何types,只要换出Integer::max和/或HashMap::new就可以了。

如果你不关心合并中出现哪个价值,那么有一个更清洁的解决scheme:

 mx = list.stream().collect(HashMap::new, Map::putAll, Map::putAll); 

而作为通用的方法:

 public static <K, V> Map<K, V> mergeMaps(Stream<? extends Map<K, V>> stream) { return stream.collect(HashMap::new, Map::putAll, Map::putAll); } public static <K, V, M extends Map<K, V>> M mergeMaps(Stream<? extends Map<K, V>> stream, BinaryOperator<V> mergeFunction, Supplier<M> mapSupplier) { return stream.collect(mapSupplier, (a, b) -> b.forEach((k, v) -> a.merge(k, v, mergeFunction)), Map::putAll); } 

我添加了对包含Stream API实用程序方法的质子包库的贡献。 以下是你如何达到你想要的:

 Map<String, Integer> mx = MapStream.ofMaps(m1, m2).mergeKeys(Integer::max).collect(); 

基本上, mergeKeys将在一个新的地图中收集键值对(提供一个合并函数是可选的,否则最终会以Map<String, List<Integer>>结尾),并且将entrySet()上的stream()得到一个新的MapStream 。 然后使用collect()来得到最终的地图。

使用StreamEx你可以做到:

 StreamEx.of(m1, m2) .flatMapToEntry(x -> x) .grouping(IntCollector.max()) 

这是工程,你可以做的只是:

 map3 = new HashMap<>(); map3.putAll(map1); map3.putAll(map2);