Java 8:计算lambda迭代的首选方法?

66 java lambda java-8 java-stream

我经常遇到同样的问题.我需要计算一个lambda的运行,以便在lambda之外使用.例如:

myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");
Run Code Online (Sandbox Code Playgroud)

问题是runCount需要是final,所以它不能是int.它不能是一个整数,因为那是不可变的.我可以使它成为类级变量(即一个字段),但我只需要在这个代码块中.我知道有各种各样的方式,我只是好奇你最喜欢的解决方案是什么?您使用AtomicInteger或数组引用还是其他方式?

Stu*_*rks 64

为了讨论,让我重新格式化你的例子:

long runCount = 0L;
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount++; // doesn't work
    });
System.out.println("The lambda ran " + runCount + " times");
Run Code Online (Sandbox Code Playgroud)

如果你真的需要从lambda中增加一个计数器,那么这样做的典型方法是使计数器为a AtomicIntegerAtomicLong然后调用其中的一个增量方法.

您可以使用单个元素intlong数组,但如果流并行运行,则会出现竞争条件.

但请注意,流结束forEach,这意味着没有返回值.您可以将项目更改forEach为a peek,然后计算它们:

long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
    })
    .count();
System.out.println("The lambda ran " + runCount + " times");
Run Code Online (Sandbox Code Playgroud)

这有点好,但仍然有点奇怪.其原因是,forEachpeek只能通过副作用做好自己的工作.Java 8的新兴功能风格是避免副作用.我们通过将计数器的增量提取到count流上的操作中来做了一点.其他典型的副作用是将项目添加到集合中.通常这些可以通过使用收集器来替换.但是,如果不知道你想要做什么实际工作,我不能提出更具体的建议.

  • 声明`final AtomicInteger i = new AtomicInteger(1);`和lambda中的某个地方使用`i.getAndAdd(1)`.停止并记住`int i = 1; ......我以前都是. (11认同)
  • 应该注意的是,一旦`count`实现开始使用`SIZED`流的快捷方式,`peek`就会停止工作.这可能永远不会成为`filter`ed流的问题,但如果有人稍后更改代码,可能会产生很大的惊喜...... (6认同)
  • 如果Java在数字类(包括AtomicInteger)上实现诸如Incrementable之类的接口,并将诸如++之类的运算符声明为看似功能,则我们不需要运算符重载并且仍然具有可读性强的代码。 (2认同)

小智 27

作为同步攻击AtomicInteger的替代方法,可以使用整数数组.只要对数组引用没有分配另一个数组(并且就是这一点),它就可以用作最终变量,而字段的可以任意改变.

    int[] iarr = {0}; // final not neccessary here if no other array is assigned
    stringList.forEach(item -> {
            iarr[0]++;
            // iarr = {1}; Error if iarr gets other array assigned
    });
Run Code Online (Sandbox Code Playgroud)

  • 我认为,对于直接在集合上(没有流)的简单 `foreach`,这是一个足够好的方法。谢谢 !! (2认同)

小智 12

AtomicInteger runCount = 0L;
long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
        runCount.incrementAndGet();
    });
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
Run Code Online (Sandbox Code Playgroud)

  • 请[编辑]了解更多信息.仅限代码和"尝试此"答案是[气馁](// meta.stackexchange.com/questions/196187),因为它们不包含可搜索的内容,也不解释为什么有人应该"试试这个".我们努力成为知识的资源. (14认同)
  • 你的回答让我困惑.你有两个名为`runCount`的变量.我怀疑你打算只有一个,但是哪一个? (6认同)
  • 1) 此代码无法编译:流没有返回 long 的终端操作 2) 即使可以,“runCount”值也将始终为“1”: - 流没有终端操作,因此 peek() lambda 参数永远不会被调用 - System.out 行在显示之前增加运行计数 (3认同)
  • `AtomicInteger` 帮助了我,但我会用 `new AtomicInteger(0)` 初始化它 (2认同)

Ahm*_*han 11

不应该使用 AtomicInteger,你不应该使用东西,除非你有很好的理由使用。使用 AtomicInteger 的原因可能是只允许并发访问等。

当涉及到您的问题时;

Holder 可用于在 lambda 中保持和递增它。然后你可以通过调用 runCount.value 得到它

Holder<Integer> runCount = new Holder<>(0);

myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount.value++; // now it's work fine!
    });
System.out.println("The lambda ran " + runCount + " times");
Run Code Online (Sandbox Code Playgroud)

  • 真的吗?为什么? (2认同)
  • 我同意 - 如果我知道我没有在 lamda/stream 中执行任何并发操作,为什么我要使用旨在满足并发性的 AtomicInteger - 这可能会引入锁定等,即很多年前的原因, JDK 引入了一组新的集合及其迭代器,它们不执行任何锁定 - 为什么要在许多场景不需要锁定时增加性能降低能力(例如锁定)的负担。 (2认同)
  • `Stream.forEach` 是明确不确定的,因此使用 `Holder` 可能会也可能不会起作用,具体取决于底层流。 (2认同)
  • @GeroldBroser 对于调用线程来说它也是不确定的:“对于任何给定的元素,可以在库选择的任何时间和任何线程中执行该操作”。这可能会导致竞争条件导致结果无效。 (2认同)

Jos*_*oll 6

对我来说,这成功了,希望有人觉得它有用:

AtomicInteger runCount = new AtomicInteger(0);
myStream.stream().filter(...).forEach(item -> runCount.getAndIncrement());
System.out.println("The lambda ran " + runCount.get() + "times");
Run Code Online (Sandbox Code Playgroud)

getAndIncrement() Java 文档指出:

原子地递增当前值,具有 VarHandle.getAndAdd 指定的内存效果。等效于 getAndAdd(1)。


shm*_*sel 5

如果您不想创建字段,因为您只在本地需要它,则可以将其存储在匿名类中:

int runCount = new Object() {
    int runCount = 0;
    {
        myStream.stream()
                .filter(...)
                .peek(x -> runCount++)
                .forEach(...);
    }
}.runCount;
Run Code Online (Sandbox Code Playgroud)

很奇怪,我知道。但它确实使临时变量超出了本地范围。

  • 这里到底发生了什么,需要更多解释,谢谢 (2认同)
  • @MrCholo这是一个[初始化块](https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html)。它在构造函数之前运行。 (2认同)
  • @MrCholo 不,它是一个实例初始值设定项。 (2认同)
  • @MrCholo 匿名类不能有显式声明的构造函数。 (2认同)
  • @OlivierGrégoire 我承认乍一看很令人困惑,但我不确定为什么你认为存在性能问题。这里的大多数解决方案都涉及对象创建。 (2认同)

小智 5

另一种选择是使用 apache commons MutableInt。

MutableInt cnt = new MutableInt(0);
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        cnt.increment();
    });
System.out.println("The lambda ran " + cnt.getValue() + " times");
Run Code Online (Sandbox Code Playgroud)