在Java 8中使用Lambda表达式将流收集到HashMap中

50

我有一个 HashMap,需要使用某些函数进行过滤:

HashMap<Set<Integer>, Double> container
Map.Entry<Set<Integer>, Double> map = container.entrySet()
            .stream()
            .filter(k -> k.getKey().size() == size)

对于size = 2,以下应为有效:

containerBeforeFilter = {1,2,3} -> 1.5, {1,2} -> 1.3
containerAfterFilter = {1,2} -> 1.3

在我使用过滤器函数之后,我想再次将结果收集到HashMap中。但是,当我尝试应用此处建议的方法时,我得到了非法语句。

因此,在过滤器之后应用以下语句是不合法的:

.collect(Collectors.toMap((entry) -> entry.getKey(), (entry) -> entry.getValue()));

收集未改变的映射值的正确方法是什么,其中唯一的条件是满足某个键?

更新

上述代码中的错误在于变量map的声明类型。它应该是Map而不是Map.Entry

因此现在功能正常的代码是:

Map<Set<Integer>, Double> map = container.entrySet()
            .stream()
            .filter(k -> k.getKey().size() == size)
            .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));

你能告诉我你正在使用哪个Java版本吗?我无法重现你的问题。 - Flown
这是最新的JDK 8u65。 - chao
1
我也使用了最新的JDK,但这段代码对你不起作用:Map<Set<Integer>, Double> collect = container.entrySet().stream().filter(k -> k.getKey().size() == size).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - Flown
1
好的,我通过你的帮助 @Flown 找到了问题所在!问题出在 map 变量的类型上!它必须是 Map 而不是 Map.Entry。非常愚蠢的错误。现在 Collectors 也可以正常工作了!谢谢! - chao
@wero 可能可以。NetBeans 8.0.2没有抱怨并且成功构建。 - chao
你可以使用 container.keySet() 来简化。 - Manos Nikolaidis
2个回答

51
如果你的结束地图有“重复键”的风险,有一个更好的解决方案,使用{{}}。
toMap(Function<? super T, ? extends K> keyMapper,
                            Function<? super T, ? extends U> valueMapper,
                            BinaryOperator<U> mergeFunction,
                            Supplier<M> mapSupplier)

你可以像这样使用它:

HashMap<Set<Integer>, Double> map = container.entrySet()
    .stream()
    .filter(k -> k.getKey().size() == size)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (prev, next) -> next, HashMap::new));

随着添加重复键,程序将使用最新的键代替抛出异常。 最后一个参数是可选的。 如果你想将重复键保留到列表中,请改用Collectors.groupingBy

1
谢谢,你能解释一下吗?你在这里做什么?Map.Entry :: getKey,Map.Entry :: getValue,(prev,next) - > next,HashMap :: new)?? - BdEngineer
2
@Shyam,你可以在Javadocs中找到解释: Map.Entry::getKey 等同于 entry -> entry.getKey() - keyMapper 映射函数,用于生成键 Map.Entry::getValue 等同于 entry -> entry.getValue() - valueMapper 映射函数,用于生成值 (prev, next) -> next - mergeFunction 合并函数,用于解决与相同键关联的值之间的冲突,作为提供给 Map.merge(Object, Object, BiFunction) 的参数 HashMap::new 等同于 () -> new HashMap<>() - mapSupplier 函数,返回一个新的、空的 Map,将结果插入其中 - provisota
非常感谢,您能详细说明一下 """ (prev, next) -> next - mergeFunction 合并函数,用于解决与Map.merge(Object, Object, BiFunction)提供的相同键相关联的值之间的冲突 """ 您在哪里提到合并? - BdEngineer
1
这里是翻译过后的文本: .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (prev, next) -> next, HashMap::new)); collect返回HashMap吗?HashMap需要(K & V),为什么在这里传递了4个参数...它是如何工作的?这里的键是什么,返回HashMap的值是什么? - BdEngineer

31

看起来在您的示例中,Collectors.toMap 没有捕获 stream.collect 的类型参数,只返回了一个 Map<Object,Object>

作为一种解决方法,您可以自己创建结果映射,并在最后的流步骤中将过滤后的条目添加到结果映射中:

Map<Set<Integer>, Double> result = new HashMap<>();
container.entrySet()
    .stream()
    .filter(entry -> entry.getKey().size() == size)
    .forEach(entry -> result.put(entry.getKey(), entry.getValue()));

是的,这种方法很有效!虽然,为什么另一种方法失败了有点奇怪。谢谢! - chao
1
Collectors.toMap 存在 Bug。它不接受重复的值。 - RathanaKumar
如果您有重复键的机会,请查看其他答案...但在这种情况下,您不需要这样做,因为您知道输入最初是一个哈希。 - rogerdpack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接