【问题标题】:Issue with Java 8 Lambda for effective final while incrementing countsJava 8 Lambda 在增加计数时有效的最终问题
【发布时间】:2017-03-22 00:46:50
【问题描述】:

我想在以下场景中使用 Java 8 Lambda 表达式,但我得到 在封闭范围内定义的局部变量 fooCount 必须是最终的或有效的最终。我理解错误消息的内容,但我需要在这里计算百分比,所以需要增加 fooCountbarCount 然后计算百分比。那么实现它的方法是什么:

        // key is a String with values like "FOO;SomethinElse" and value is Long
        final Map<String, Long> map = null;
    ....
    private int calculateFooPercentage() {
        long fooCount = 0L;
        long barCount = 0L;

        map.forEach((k, v) -> {
            if (k.contains("FOO")) {
                fooCount++;
            } else {
                barCount++;
            }
        });

        final int fooPercentage = 0;
        //Rest of the logic to calculate percentage
        ....
        return fooPercentage;
    }

我有一个选择是在这里使用AtomicLong 而不是long,但我想避免它,所以以后如果可能我想在这里使用并行流。

【问题讨论】:

  • 也许你应该使用stream.count()
  • 如果你能做到这一点,你会得到并行流的竞争条件,除非你使用AtomicLong。理想情况下,您会使用更实用的样式,例如 khelwood 的答案,它适用于并行流
  • 一个简单的解决方案是不要将变量设置为本地变量。如果你让它们成为类的成员,那么你可以很高兴地在 lambda 表达式中更新它们。但是,正如答案所指出的那样,除非您使变量成为原子变量或使用其他同步机制,否则这在并行流中不起作用。

标签: java lambda java-8 final


【解决方案1】:

流中有一个count 方法可以为您进行计数。

long fooCount = map.keySet().stream().filter(k -> k.contains("FOO")).count();
long barCount = map.size() - fooCount;

如果您想要并行化,请将.stream() 更改为.parallelStream()

或者,如果您尝试手动增加变量并使用流并行化,那么您可能希望使用AtomicLong 之类的东西来保证线程安全。一个简单的变量,即使编译器允许,也不是线程安全的。

【讨论】:

  • 感谢您的回答。您能否在这里以线程安全的方式展示如何使用并行化?
  • @Learner 是的,我已将其添加到我的答案中。
【解决方案2】:

要同时获取数字、匹配和不匹配元素,您可以使用

Map<Boolean, Long> result = map.keySet().stream()
    .collect(Collectors.partitioningBy(k -> k.contains("FOO"), Collectors.counting()));
long fooCount = result.get(true);
long barCount = result.get(false);

但是由于您的来源是Map,它知道它的总大小,并且想要计算一个百分比,不需要barCount,这个特定任务可以解决为

private int calculateFooPercentage() {
    return (int)(map.keySet().stream().filter(k -> k.contains("FOO")).count()
                 *100/map.size());
}

两种变体都是线程安全的,即将stream() 更改为parallelStream() 将并行执行操作,但是,此操作不太可能从并行处理中受益。您将需要大量的键字符串或映射来获得好处……

【讨论】:

  • 不错的答案,最好的简洁:)
  • @Holger,感谢您的回答。我还需要在百分比计算中使用long 值。所以基本上我想要所有 values 的总和,其中 key 包含 "FOO" 而不仅仅是 count()
  • 我把它改成了:return (int)(map.entrySet().stream() .filter(e -&gt; e.getKey().contains("SLD")).mapToLong(e -&gt; e.getValue().longValue()).sum() * 100 / map.size());
  • 不需要显式拆箱,即.mapToLong(e -&gt; e.getValue()) 就足够了。这也可以使用方法引用.mapToLong(Map.Entry::getValue) 编写。但是,总和与地图大小(即计数)之间的比率不是百分比。您实际上需要计算long total=map.values().stream().mapToLong(Long::longValue).sum();,然后是/*yourcode*/ *100/total
  • 感谢@Holger 提供详细信息。
【解决方案3】:

我同意其他答案,表明您应该使用countpartitioningBy

只是用一个例子来解释原子性问题,考虑下面的代码:

private static AtomicInteger i1 = new AtomicInteger(0);
private static int i2 = 0;

public static void main(String[] args) {
    IntStream.range(0, 100000).parallel().forEach(n -> i1.incrementAndGet());
    System.out.println(i1);

    IntStream.range(0, 100000).parallel().forEach(n -> i2++);
    System.out.println(i2);
}

对于i1,这将返回 100000 的预期结果,但对于 i2,返回一个小于该值的不确定数字(在我的测试运行中介于 50000 和 80000 之间)。原因应该很明显了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-10
    相关资源
    最近更新 更多