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


82

我经常遇到同样的问题。我需要计算在Lambda之外使用的Lambda的运行次数。例如:

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

问题是runCount必须是final,因此不能为int。它不能是整数,因为那是不可变的。我可以将其设置为类级别的变量(即字段),但是在此代码块中只需要它即可。我知道有各种各样的方法,我很好奇您对此的首选解决方案是什么?您是否使用AtomicInteger或数组引用或其他某种方式?


7
@ Sliver2009不,不是。
罗希特·贾因

4
@Florian您必须在AtomicInteger这里使用。
罗希特·贾因

Answers:


70

为了讨论起见,让我重新格式化您的示例:

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

如果您确实需要在Lambda中递增计数器,则通常的做法是将计数器设为AtomicIntegerAtomicLong,然后在其上调用递增方法之一。

您可以使用单元素intlong数组,但是如果流并行运行,则将具有竞争条件。

但是请注意,流以结尾forEach,这意味着没有返回值。您可以将更forEach改为peek,使项目通过,然后对它们进行计数:

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

这好一些,但仍然有些奇怪。原因是forEach并且peek只能通过副作用来完成他们的工作。Java 8的新兴功能风格是避免副作用。通过将计数器的增量提取到count流中的操作中,我们做了一些工作。其他典型的副作用是将项目添加到集合中。通常这些可以通过使用收集器来替换。但是,在不知道您要实际执行什么工作的情况下,我无法提出更具体的建议。


8
应该注意的是peek,一旦count实现开始使用SIZED流的快捷方式,它将停止工作。filtered stream可能永远都不是问题,但是如果以后有人更改代码,可能会带来很大的惊喜……
Holger 2015年

14
声明final AtomicInteger i = new AtomicInteger(1);并使用lambda i.getAndAdd(1)。停下来,记住int i=1; ... i++曾经有多美好。
aliopi 2015年

2
如果Java会Incrementable在数字类(包括)上实现接口AtomicInteger,并声明诸如++看似功能的运算符,那么我们就不需要运算符重载并且仍然具有可读性强的代码。
SeverityOne

37

作为同步麻烦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
    });

如果要确保引用没有分配其他数组,可以将iarr声明为最终变量。但是正如@pisaruk指出的那样,这不能并行工作。
themathmagician

1
我认为,对于foreach直接基于集合的简单操作(不带流),这是一种足够好的方法。谢谢 !!
Sabir Khan

只要您不并行运行,这是最简单的解决方案。
David DeMar '18

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

15
编辑更多信息。代码只和“试试这个”答案是不鼓励的,因为它们不包含搜索的内容,并没有解释为什么有人要“试试这个”。我们在这里努力成为知识的资源。
Mogsdad '16

7
您的回答使我感到困惑。您有两个都名为的变量runCount。我怀疑您打算只拥有其中之一,但是哪一个呢?
Ole VV

1
我发现runCount.getAndIncrement()更合适。好答案!
kospol

2
AtomicInteger帮助我,但我将与初始化它new AtomicInteger(0)
斯特凡Höltker

1)此代码将无法编译:流没有返回长整数的终端操作2)即使可以,'runCount'值也始终为'1':-流没有终端操作,因此peek()lambda参数永远不会被称为-的System.out线显示它之前增加运行计数
塞德里克

8

不应该使用AtomicInteger,除非您有充分的使用理由,否则不应该使用事物。使用AtomicInteger的原因可能仅是允许并发访问等。

当涉及到您的问题时;

固定器可用于在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");

JDK中有一些Holder类。这似乎是javax.xml.ws.Holder
布拉德·库比

1
真?又为什么呢
lkahtz

1
我同意-如果我知道我没有在lamda / stream中执行任何并发操作,为什么我要使用旨在满足并发性的AtomicInteger-这可能会引入锁定等,也就是为什么很多年前, JDK引入了一组新的集合及其迭代器,它们没有执行任何锁定操作-为什么在许多情况下不需要锁定时,为什么要增加性能降低功能,例如锁定功能。
大众

4

对我来说,这是成功的窍门,希望有人发现它有用:

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

getAndIncrement()Java文档指出:

以原子方式递增当前值,并具有VarHandle.getAndAdd指定的存储效果。等效于getAndAdd(1)。


3

这样做的另一种方式(如果只希望在某些情况下(例如,如果操作成功的话)仅增加计数)很有用,可以使用mapToInt()and sum()

int count = myStream.stream()
    .filter(...)
    .mapToInt(item -> { 
        foo();
        if (bar()){
           return 1;
        } else {
           return 0;
    })
    .sum();
System.out.println("The lambda ran " + count + "times");

正如Stuart Marks指出的那样,这仍然有些奇怪,因为它不能完全避免副作用(取决于所采取的措施foo()bar()所做的事情)。

在lambda之外可以访问的另一种增加变量的方法是使用类变量:

public class MyClass {
    private int myCount;

    // Constructor, other methods here

    void myMethod(){
        // does something to get myStream
        myCount = 0;
        myStream.stream()
            .filter(...)
            .forEach(item->{
               foo(); 
               myCount++;
        });
    }
}

在此示例中,在一种方法中将类变量用于计数器可能没有意义,因此,除非有充分的理由,否则我不建议这样做。final如果可能的话,保留类变量在线程安全性等方面会有所帮助(有关使用的讨论,请参见http://www.javapractices.com/topic/TopicAction.do?Id=23final)。

为了更好地了解lambda为何以这种方式工作,https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood进行了详细介绍。


3

如果由于只需要本地而不想创建字段,则可以将其存储在匿名类中:

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

很奇怪,我知道。但这确实使临时变量甚至不在本地范围内。


2
这到底是怎么回事,需要更多解释吗
亚历山大·米尔斯

基本上,它是在匿名类上递增和检索字段。如果您告诉我您特别困惑的地方,我可以尝试澄清一下。
shmosel

1
@MrCholo这是一个初始化程序块。它在构造函数之前运行。
shmosel

1
@MrCholo不,它是一个实例初始化器。
shmosel

1
@MrCholo匿名类不能具有显式声明的构造函数。
shmosel

1

减少也可以,您可以像这样使用

myStream.stream().filter(...).reduce((item, sum) -> sum += item);

1

另一种选择是使用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");

0
AtomicInteger runCount = new AtomicInteger(0);

elements.stream()
  //...
  .peek(runCount.incrementAndGet())
  .collect(Collectors.toList());

// runCount.get() should have the num of times lambda code was executed
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.