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

后端 未结 11 1725
没有蜡笔的小新
没有蜡笔的小新 2021-02-01 00:17

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


        
相关标签:
11条回答
  • 2021-02-01 00:41
    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
    
    0 讨论(0)
  • 2021-02-01 00:44

    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");
    
    0 讨论(0)
  • 2021-02-01 00:46

    reduce also works,you can use it like this

    myStream.stream().filter(...).reduce((item, sum) -> sum += item);
    
    0 讨论(0)
  • 2021-02-01 00:50

    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.

    0 讨论(0)
  • 2021-02-01 00:52

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题