简体   繁体   English

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

[英]Java 8: preferred way to count iterations of a lambda?

I face the same problem often.我经常面临同样的问题。 I need to count the runs of a lambda for use outside the lambda .我需要计算lambda的运行次数,以便在lambda之外使用。

Eg:例如:

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 .问题是runCount需要是final ,所以它不能是int It cannot be an Integer because that's immutable .它不能是Integer因为它是不可变的 I could make it class level variable (ie 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?您使用AtomicInteger还是数组引用或其他方式?

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 really need to increment a counter from within a lambda, the typical way to do so is to make the counter an AtomicInteger or AtomicLong and then call one of the increment methods on it.如果您确实需要从 lambda 中递增计数器,那么典型的方法是将计数器设为AtomicIntegerAtomicLong ,然后对其调用递增方法之一。

You could use a single-element int or long array, 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.但请注意,流以forEach结尾,这意味着没有返回值。 You could change the forEach to a peek , which passes the items through, and then count them:您可以将forEach更改为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 forEach and peek can only do their work via side effects.原因是forEachpeek只能通过副作用来完成它们的工作。 The emerging functional style of Java 8 is to avoid side effects. Java 8 新兴的函数式风格是为了避免副作用。 We did a little of that by extracting the increment of the counter into a count operation on the stream.通过将计数器的增量提取到流上的count操作中,我们做了一些工作。 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.但是在不知道您要尝试做的实际工作的情况下,我无法提出更具体的建议。

As an alternative to sync hassling AtomicInteger one could use an integer array instead.作为同步问题 AtomicInteger 的替代方案,可以改用整数数组 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 final variable while the values of the fields can change arbitrarily.只要对数组引用没有分配另一个数组(这就是重点),它就可以用作最终变量,而字段的可以任意更改

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

You shouldn't use AtomicInteger, you shouldn't use things unless you have a really good reason to use.不应该使用 AtomicInteger,你不应该使用东西,除非你有很好的理由使用。 And the reason for using AtomicInteger might be only allowing concurrent accesses or such as.使用 AtomicInteger 的原因可能是只允许并发访问等。

When it comes to your problem;当涉及到您的问题时;

Holder can be use for holding and incrementing it inside a lambda. Holder 可用于在 lambda 中保持和递增它。 And after you can get it by calling runCount.value然后你可以通过调用 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");

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.原子地递增当前值,具有 VarHandle.getAndAdd 指定的内存效果。 Equivalent to getAndAdd(1).等效于 getAndAdd(1)。

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.但它确实将临时变量保持在局部范围之外。

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

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()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 final if possible can be helpful in terms of thread safety, etc (see http://www.javapractices.com/topic/TopicAction.do?Id=23 for a discussion on using final ).如果可能,将类变量保持为final在线程安全等方面会有所帮助(有关使用final的讨论,请参见http://www.javapractices.com/topic/TopicAction.do?Id=23 )。

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-Hood has a detailed look.为了更好地了解 lambda 的工作方式, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood有一个详细的介绍。

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

myStream.stream().filter(...).reduce((item, sum) -> sum += item);
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

An enum can be used, too.也可以使用enum Especially if you have more than one counter in an iteration:特别是如果您在迭代中有多个计数器:

import java.util.Arrays;

class LambdaCounter {

    enum CountOf {

        NO,
        OK,
        ERROR;

        private int count;

        // can be named inc(), instead of the Greek capital Delta,
        // which stands for the math increment operator '∆' <https://en.wikipedia.org/wiki/%E2%88%86>
        synchronized int Δ( final int... times ) {

            if ( times.length <= 0 )
                return ++count; // increase by 1

            return count += Arrays.stream( times ).sum(); // increase by arguments
        }

        // can be named val[ue](), instead of the Greek capital Xi,
        // which stands for the math identity operator '≡' <https://en.wikipedia.org/wiki/Triple_bar>
        int Ξ() {
            return count;
        }
    }

    public static void main( final String[] args ) {

        Arrays.stream( new int[] { 1, 2, 3, 4, 5, 6, 7 } )
            .forEach( i -> {
                CountOf.NO.Δ();
                @SuppressWarnings( "unused" )
                final int LHS_DUMMY =
                    i % 2 == 0
                        ? CountOf.OK.Δ()
                        : CountOf.ERROR.Δ();
            } );
        System.out.printf( "No: %d, OK: %d, Error: %d, Error.inc(38): %d, Error.inc(4, 4): %d%n",
            CountOf.NO.Ξ(), CountOf.OK.Ξ(), CountOf.ERROR.Ξ(), CountOf.ERROR.Δ( 38 ), CountOf.ERROR.Δ( 4, 4 ) );

        // Output:
        // No: 7, OK: 3, Error: 4, Error.inc(38): 42, Error.inc(4, 4): 50
    }
}

For me, this is the most elegant way.对我来说,这是最优雅的方式。

long count = list.stream()
  .peek(/* do your stuff here */)
  .long();

There is a bug in JDK 9,10 that prevents the above solution from working, but you can work around it as follows. JDK 9,10 中存在一个错误,导致上述解决方案无法工作,但您可以按如下方式解决该问题。 https://bugs.openjdk.java.net/browse/JDK-8198356 https://bugs.openjdk.java.net/browse/JDK-8198356

long count = list.stream()
  .peek(/* do your stuff here */)
  .collect(Collectors.counting());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM