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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28790784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 06:49:20  来源:igfitidea点击:

Java 8: Preferred way to count iterations of a lambda?

javalambdajava-8java-stream

提问by

I face the same problem often. I need to count the runs of a lambda for use outside the lambda. E.g.:

我经常面临同样的问题。我需要计算 lambda 的运行次数,以便在 lambda 之外使用。例如:

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

The issue is that runCount needs to be final, so it cannot be an int. It cannot be an Integer because that's immutable. I could make it class level variable (i.e. a field) but I'll only need it in this block of code. I know there are various ways, I'm just curious what is your preferred solution for this? Do you use an AtomicInteger or an array reference or some other way?

问题是 runCount 需要是 final,所以它不能是 int。它不能是整数,因为它是不可变的。我可以使它成为类级别的变量(即一个字段),但我只需要在这个代码块中使用它。我知道有多种方法,我只是想知道您对此的首选解决方案是什么?您使用 AtomicInteger 还是数组引用或其他方式?

回答by Stuart Marks

Let me reformat your example a bit for the sake of discussion:

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

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

If you reallyneed to increment a counter from within a lambda, the typical way to do so is to make the counter an AtomicIntegeror AtomicLongand then call one of the increment methods on it.

如果您确实需要从 lambda 中递增计数器,那么典型的方法是使计数器成为AtomicIntegeror AtomicLong,然后在其上调用递增方法之一。

You could use a single-element intor longarray, but that would have race conditions if the stream is run in parallel.

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

But notice that the stream ends in forEach, which means that there is no return value. You could change the forEachto a peek, which passes the items through, and then count them:

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

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

This is somewhat better, but still a bit odd. The reason is that forEachand peekcan only do their work via side effects. The emerging functional style of Java 8 is to avoid side effects. We did a little of that by extracting the increment of the counter into a countoperation on the stream. Other typical side effects are adding items to collections. Usually these can be replaced via use of collectors. But without knowing what actual work you're trying to do, I can't suggest anything more specific.

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

回答by Leandruz

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

回答by Duke Spray

As an alternative to sync hassling AtomicInteger one could use an integer arrayinstead. As long as the reference to the array does not get another array assigned(and that's the point) it can be used as a finalvariable while the valuesof the fields can changearbitrarily.

作为同步问题 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
    });

回答by A. D.

Another way of doing this (useful if you'd like your count to only be incremented in some cases, like if an operation was successful) is something like this, using mapToInt()and sum():

另一种这样做的方法(如果您希望您的计数仅在某些情况下增加,例如操作成功,则很有用)是这样的,使用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");

As Stuart Marks noted, this is still somewhat odd, because it's not completely avoiding side effects (depending on what foo()and bar()are doing).

正如 Stuart Marks 所指出的,这仍然有些奇怪,因为它并没有完全避免副作用(取决于什么foo()bar()正在做什么)。

And another way of incrementing a variable in a lambda that's accessible outside of it is to use a class variable:

另一种在 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++;
        });
    }
}

In this example, using a class variable for a counter in one method probably doesn't make sense, so I'd caution against it unless there's a good reason to. Keeping class variables finalif possible can be helpful in terms of thread safety, etc (see http://www.javapractices.com/topic/TopicAction.do?Id=23for a discussion on using final).

在这个例子中,在一个方法中使用一个类变量作为一个计数器可能没有意义,所以我会警告不要这样做,除非有很好的理由。final如果可能,保留类变量在线程安全等方面会有所帮助(有关使用的讨论,请参见http://www.javapractices.com/topic/TopicAction.do?Id=23final)。

To get a better idea of why lambdas work the way they do, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hoodhas a detailed look.

为了更好地了解 lambda 的工作方式,https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood有一个详细的介绍。

回答by shmosel

If you don't want to create a field because you only need it locally, you can store it in an anonymous class:

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

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

Weird, I know. But it does keep the temporary variable out of even local scope.

很奇怪,我知道。但它确实将临时变量保持在局部范围之外。

回答by José Ripoll

For me, this did the trick, hopefully someone finds it useful:

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

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

getAndIncrement() Java documentation states :

getAndIncrement() Java 文档说明:

Atomically increments the current value, with memory effects as specified by VarHandle.getAndAdd. Equivalent to getAndAdd(1).

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

回答by yao.qingdong

reduce also works,you can use it like this

reduce 也有效,你可以像这样使用它

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

回答by Ahmet Orhan

You shouldn'tuse AtomicInteger, you shouldn't use things unless you have a really good reason to use. And the reason for using AtomicInteger might be only allowing concurrent accesses or such as.

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

When it comes to your problem;

当涉及到您的问题时;

Holder can be use for holding and incrementing it inside a lambda. And after you can get it by calling runCount.value

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");

回答by tsunllly

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 Vojtěch Fried

Another alternative is to use apache commons MutableInt.

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