113

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

E.g.:

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?

informatik01
  • 16,038
  • 10
  • 74
  • 104

12 Answers12

83

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.

You could use a single-element int or long array, but that would have race conditions if the stream is run in parallel.

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

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. 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 count operation 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.

Shastick
  • 1,218
  • 1
  • 12
  • 29
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • 9
    It should be noted that `peek` stops working, once `count` implementations start using shortcuts for `SIZED` streams. That may never be an issue with `filter`ed stream but can create great surprises if someone changes the code at a later time… – Holger Mar 02 '15 at 09:08
  • 26
    Declare `final AtomicInteger i = new AtomicInteger(1);` and somewhere in your lambda use `i.getAndAdd(1)`. Stop and remember how nice `int i=1; ... i++` used to be. – aliopi Oct 08 '15 at 13:49
  • 4
    If Java would implement interfaces such as `Incrementable` on numeric classes, including `AtomicInteger`, and declare operators such as `++` to be fancy-looking functions, we wouldn't need operator overloading and still have very readable code. – SeverityOne Apr 23 '18 at 12:02
48

As an alternative to sync hassling AtomicInteger one could use an integer array instead. 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
    });
Duke Spray
  • 589
  • 4
  • 5
  • If you want to make sure that the reference does not get another array assigned, you can declare iarr as a final variable. But as @pisaruk points out, this won't work in parallel. – themathmagician Aug 24 '17 at 17:35
  • 3
    I think, for simple `foreach` directly on collection ( without streams ) , this is a good enough approach. thanks !! – Sabir Khan Feb 14 '18 at 11:43
  • 1
    This is the simplest solution, as long as you're not running things in parallel. – David DeMar Apr 30 '18 at 14:46
18

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 :

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

informatik01
  • 16,038
  • 10
  • 74
  • 104
José Ripoll
  • 504
  • 1
  • 7
  • 17
  • i have ``` AtomicInteger rowCount = new AtomicInteger(0); items.stream() .map(x -> (String.format("%3s. %-45s - count: %s", rowCount.getAndIncrement(), x.getName(), x.getSize()))) .forEach(System.out::println); ``` – Sasha Bond Mar 02 '22 at 18:41
16
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
        runCount.incrementAndGet();
    });
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
Neeraj B.
  • 455
  • 1
  • 6
  • 12
Leandruz
  • 265
  • 2
  • 2
  • 19
    Please [edit] with more information. Code-only and "try this" answers are [discouraged](//meta.stackexchange.com/questions/196187), because they contain no searchable content, and don't explain why someone should "try this". We make an effort here to be a resource for knowledge. – Mogsdad Mar 21 '16 at 16:50
  • 7
    Your answer confuses me. You got two variables both named `runCount`. I suspect you intended to have only one of them, but which one? – Ole V.V. Jan 20 '17 at 12:23
  • 1
    I found the runCount.getAndIncrement() to be more suitable. Great answer! – kospol May 15 '17 at 20:24
  • 5
    The `AtomicInteger`helped me out but i would init it with `new AtomicInteger(0)` – Stefan Höltker May 22 '19 at 07:49
  • 3
    1) This code won't compile : the stream has no terminal operation that returns a long 2) Even if it would, 'runCount' value would always be '1' : - the stream has no terminal operation so peek() lambda argument will never be called - System.out line increments the run count before displaying it – Cédric Oct 16 '20 at 09:30
12

You shouldn't use 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.

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<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");
Ahmet Orhan
  • 304
  • 3
  • 12
  • 1
    There's a few Holder classes in the JDK. This one seems to be `javax.xml.ws.Holder`. – Brad Cupit Feb 06 '20 at 17:02
  • 3
    Really? And why? – lkahtz Jul 22 '20 at 07:02
  • 2
    I agree - if I know I'm not doing any concurrent operations in the lamda/stream why would I want to use AtomicInteger that is designed to cater for concurrency - that might introduce locking etc., i.e. the reason why, many years ago, the JDK introduced the new set of collections and their iterators that did not perform any locking - why burden something with a performance reducing capability e.g. locking when locking is not required for many scenarios. – Volksman Oct 18 '20 at 00:06
  • Linkservice: [`javax.xml.ws.Holder`](https://docs.oracle.com/javaee/7/api/javax/xml/ws/Holder.html). – Gerold Broser Jan 18 '21 at 20:00
  • 2
    `Stream.forEach` is explicitly nondeterministic so using `Holder` might or might not work depending on the underlying stream. – michid Jan 20 '21 at 10:21
  • @michid [`Stream.forEach`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/stream/Stream.html#forEach(java.util.function.Consumer)) is nondeterministic regarding _the order_ of its items but _not_ regarding the count of iterations: „_Performs an action for each element of this stream._ [...] _For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream_“. – Gerold Broser Jan 20 '21 at 12:42
  • 2
    @GeroldBroser it is also non deterministic regarding the calling thread: "For any given element, the action may be performed at whatever time and in whatever thread the library chooses". This can result in race conditions rendering the result invalid. – michid Jan 20 '21 at 13:21
  • @michid I see.That means I have to use `synchronized int inc()` in [my `enum` solution](https://stackoverflow.com/a/65780941/1744774),right? – Gerold Broser Jan 20 '21 at 14:34
  • @GeroldBroser, yes synchronized should work. However, in general I try to stay away from relying on side effects when dealing with streams and lambdas. In cases like this I prefer good old loops. – michid Jan 23 '21 at 15:40
5

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.

shmosel
  • 49,289
  • 6
  • 73
  • 138
  • 2
    what on earth is going on here, in need of a little more explanation thx – Alexander Mills Jan 28 '19 at 08:54
  • Basically, it's incrementing and retrieving a field on an anonymous class. If you tell me what you're specifically confused by, I can try to clarify. – shmosel Aug 05 '19 at 20:20
  • is the code block {...} overriding or chaining the constructor of Object? –  Aug 05 '19 at 21:56
  • 2
    @MrCholo It's an [initializer block](https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html). It runs before the constructor. – shmosel Aug 05 '19 at 22:10
  • that's interesting, I see, it's a static initializer, not an instance initalizer right –  Aug 06 '19 at 00:02
  • so doesn't it need the `static` keyword in front of it or no? –  Aug 06 '19 at 00:03
  • 2
    @MrCholo No, it's an instance initializer. – shmosel Aug 06 '19 at 00:07
  • i didn't know that was possible, I thought initializers had to be static, that's cool –  Aug 06 '19 at 00:12
  • why not just use a constructor and just call super(), is this just less verbose? –  Aug 06 '19 at 00:13
  • 2
    @MrCholo An anonymous class cannot have an explicitly declared constructor. – shmosel Aug 06 '19 at 00:17
  • While this solution is clever, it's horrible because it requires creating and instantiating objects for the mere goal of avoiding scopes. I'd refuse this in any code review based on the fact that it's confusing and overkill, performance-wise. – Olivier Grégoire Jan 20 '21 at 10:50
  • 2
    @OlivierGrégoire I'll grant that it's confusing at first glance, but I'm not sure why you think there's a performance concern. Most of the solutions here involve object creation. – shmosel Jan 20 '21 at 15:39
5

Another alternative is to use 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");
Vojtěch Fried
  • 111
  • 2
  • 5
  • Linkservice: [`org.apache.commons.lang.mutable.MutableInt`](https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/mutable/MutableInt.html) – Gerold Broser Jan 18 '21 at 20:10
3

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():

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).

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

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).

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.

A. D.
  • 104
  • 5
3

For me, this is the most elegant way.

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

There is a bug in JDK 9,10 that prevents the above solution from working, but you can work around it as follows. https://bugs.openjdk.java.net/browse/JDK-8198356

long count = list.stream()
  .peek(/* do your stuff here */)
  .collect(Collectors.counting());
sxc731
  • 2,418
  • 2
  • 28
  • 30
xhaggi
  • 51
  • 2
  • I like the "style" of this solution as it's purely functional and doesn't rely on shared mutable state. Note however, that comments and the resolution of the OpenJDK ticket make it clear that the observed behaviour is not a bug but rather a performance enhancement. This is because the `count()` terminal operation can determine its result without even invoking the `peek()` portion of the pipeline; since `peek()` cannot alter the actual count. – sxc731 Jul 30 '23 at 08:14
2

reduce also works,you can use it like this

myStream.stream().filter(...).reduce((item, sum) -> sum += item);
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
tsunllly
  • 1,568
  • 1
  • 13
  • 15
0

An enum can be used, too. 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
    }
}
Gerold Broser
  • 14,080
  • 5
  • 48
  • 107