【问题标题】:How to use method reference in Java 8 for Map merge?如何在 Java 8 中使用方法引用进行 Map 合并?
【发布时间】:2019-02-25 01:54:48
【问题描述】:

我有以下两种调用收集操作的形式,都返回相同的结果,但我仍然不能完全依赖方法引用并且需要一个 lambda。

<R> R collect(Supplier<R> supplier,
          BiConsumer<R,? super T> accumulator,
          BiConsumer<R,R> combiner)

为此考虑以下由 100 个随机数组成的流

List<Double> dataList = new Random().doubles().limit(100).boxed()
            .collect(Collectors.toList());

1) 以下示例使用纯 lambdas

Map<Boolean, Integer> partition = dataList.stream()
            .collect(() -> new ConcurrentHashMap<Boolean, Integer>(),
(map, x) ->
{
    map.merge(x < 0.5 ? Boolean.TRUE : Boolean.FALSE, 1, Integer::sum);
}, (map, map2) ->
{
    map2.putAll(map);
});

2) 以下尝试使用方法引用,但第二个参数仍然需要 lambda

Map<Boolean, Integer> partition2 = dataList.stream()
            .collect(ConcurrentHashMap<Boolean, Integer>::new, 
(map, x) ->
{
    map.merge(x < 0.5 ? Boolean.TRUE : Boolean.FALSE, 1, Integer::sum);
}, Map::putAll);

如何在 java 8 中重写 collect 方法的第二个参数,以在本示例中使用方法引用而不是 lambda?

System.out.println(partition.toString());
System.out.println(partition2.toString());
{false=55, true=45}
{false=55, true=45}

【问题讨论】:

  • 为什么是map2.putAll(map)?应该是map.putAll(map2)。看看Java 9 docs。此外,您不能对第二个参数使用方法引用,因为您有条件运算符(顺便说一句,这是不必要的,x &lt; 0.5 就足够了)和初始 1 作为放入地图的值是给定键没有条目。
  • 感谢您的建议,我已经测试过并且使用 map.putAll(map2) 效果很好。那么除了合并操作之外,有没有办法在 x
  • 使用 lambda 有什么问题?当参数的数量和类型不匹配时,您不能使用方法引用。在这种情况下,merge 需要 3 个参数:键、初始值和合并二元运算符,而累加器是一个 BiConsumer,它接收映射和流的一个元素并且不返回任何内容。如果您创建一个接收映射和流元素的void 方法,则可以将其用作方法引用。但我认为这毫无意义,lambda更具表现力,IMO
  • putAll 不是适合您的操作的正确合并函数,因为它只是覆盖现有映射而不是合并值。此外x &lt; 0.5 ? Boolean.TRUE : Boolean.FALSE 已过时。只需使用x &lt; 0.5。也不需要ConcurrentMap。并且整个操作可以这么简单地执行:Map&lt;Boolean, Long&gt; partition2 = dataList.stream() .collect(Collectors.partitioningBy(x -&gt; x &lt; 0.5, Collectors.counting()));
  • 感谢 Holger 的回复,这比我尝试的所有方法都更加简洁易懂。我会接受它作为有效答案。

标签: dictionary merge java-8 collectors


【解决方案1】:

方法引用是一个方便的工具,如果你有一个现有的方法完全可以做预期的事情。如果您需要调整或附加操作,则方法引用没有特殊的语法来支持这一点,除非您将 lambda 表达式视为该语法。

当然,你可以在你的类中创建一个新方法来做你想要的事情并创建一个方法引用,当代码的复杂性增加时,这是正确的方法,因为这样,它会得到一个有意义的名字并成为可测试的。但是对于简单的代码 sn-ps,您可以使用 lambda 表达式,这只是针对相同结果的更简单的语法。从技术上讲,没有区别,只是编译器生成的持有 lambda 表达式主体的方法将被标记为“合成”。

在您的示例中,您甚至不能使用 Map::putAll 作为合并函数,因为这会覆盖第一个映射的所有现有映射,而不是合并值。

正确的实现应该是这样的

Map<Boolean, Integer> partition2 = dataList.stream()
    .collect(HashMap::new, 
             (map, x) -> map.merge(x < 0.5, 1, Integer::sum),
             (m1, m2) -> m2.forEach((k, v) -> m1.merge(k, v, Integer::sum)));

但您不需要自己实现它。 Collectors 类中已经提供了适当的内置收集器:

Map<Boolean, Long> partition2 = dataList.stream()
    .collect(Collectors.partitioningBy(x -> x < 0.5, Collectors.counting()));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-21
    • 2015-10-07
    • 1970-01-01
    • 2014-05-27
    • 2016-02-03
    相关资源
    最近更新 更多