使用 Java 8 Stream API 合并两个 Map<String, Integer>
Posted
技术标签:
【中文标题】使用 Java 8 Stream API 合并两个 Map<String, Integer>【英文标题】:Merging two Map<String, Integer> with Java 8 Stream API 【发布时间】:2014-05-27 03:34:00 【问题描述】:我有两个(或更多)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);
我怎样才能使这个测试方法变成绿色?
我和collect
和Collectors
玩了一段时间没有任何成功。
(ImmutableMap
和 newArrayList
来自 Google Guava。)
【问题讨论】:
【参考方案1】:@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);
【讨论】:
太棒了!我只需要不同的东西,而不是最大值,我需要平均值。我该怎么做? @FirasAlMannaa docs.oracle.com/javase/8/docs/api/java/util/stream/…【参考方案2】:Map<String, Integer> mx = new HashMap<>(m1);
m2.forEach((k, v) -> mx.merge(k, v, Integer::max));
【讨论】:
【参考方案3】:mx = list.stream().collect(HashMap::new,
(a, b) -> b.forEach((k, v) -> a.merge(k, v, Integer::max)),
Map::putAll);
这涵盖了任何大小列表的一般情况,应该适用于任何类型,只需根据需要换出Integer::max
和/或HashMap::new
。
如果您不关心合并中出现的值,那么有一个更简洁的解决方案:
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);
【讨论】:
【参考方案4】:我为任何可能感兴趣的人创建了 @srborlongan 所做工作的可视化表示。
【讨论】:
【参考方案5】:我将我的贡献添加到 proton pack library,其中包含 Stream API 的实用方法。您可以通过以下方式实现您想要的:
Map<String, Integer> mx = MapStream.ofMaps(m1, m2).mergeKeys(Integer::max).collect();
基本上mergeKeys
将在新映射中收集键值对(如果合并功能是可选的,否则您将得到一个Map<String, List<Integer>>
)并在entrySet()
上调用stream()
以获得一个新的MapStream
。然后使用collect()
得到结果图。
【讨论】:
【参考方案6】:使用StreamEx 你可以做到:
StreamEx.of(m1, m2)
.flatMapToEntry(x -> x)
.grouping(IntCollector.max())
【讨论】:
以上是关于使用 Java 8 Stream API 合并两个 Map<String, Integer>的主要内容,如果未能解决你的问题,请参考以下文章
Java 8 Stream根据某个相同的key合并两个Map List,其他key相同时优先保留其中一个List的值
Java 8 lambda Stream list to Map key 重复 value合并到Collection