Java streams 28. Collect 4. Counting stream elements

Terminal operation either returns one value (of the same or another type than the type of the input) or does not return anything at all (produces just side effects). It does not allow another operation to be applied after it and closes the stream.

In this post, we will continue covering the last of the terminal operations called collect():

R collect(Collector<T,A,R> collector)

It allows implementing a vast variety of algorithms using the ready-to-use implementations of collectors created by factory methods of the java.util.stream.Collectors class.



Collectors.counting() vs Stream.count() operation

The Collectors.counting() has the following signature:

Collector<T, ?, Long> counting().

The collector it creates counts a number of emitted elements of type T

   
  long c1 = Stream.of("cat", "fish", "dog")
                  .collect(Collectors.counting());
  System.out.print(c1);   //prints: 3
    

One has to keep in mind two points though:

— the stream has to be limited; otherwise, the result will never be produced;

— it iterates through all the elements of the stream.

The first point is clear: if you decide to count elements of an infinite stream, it will take an infinite time. Well, after the current value of the count grows beyond Long.LONG_MAX, which is 9,223,372,036,854,775,807 (inclusive), the result becomes negative and does not reflect the count anymore anyway.

To clarify the second point, let us count the stream elements using Stream operation count():

  
  long c2 = Stream.of("cat", "fish", "dog").count();
  System.out.print(c2);    //prints: 3
  

What’s the difference?

Why do we need two ways to calculate the number of elements?

To answer these questions, let us instrument both examples by adding an intermediate peek() operation in the middle. It does not affect the stream and just prints the value of the current element:

   
  long c3 = Stream.of("cat", "fish", "dog")
        .peek(s -> System.out.print(s))   //is not called
        .count();
  System.out.print(c3);                   //prints: 3

  long c4 = Stream.of("cat", "fish", "dog")
        .peek(s -> System.out.print(s))   //prints: catfishdog
        .collect(Collectors.counting());
  System.out.print(c4);                   //prints: 3

As you can see, in the first of the above examples the peek() operation was not invoked. The count() implementation optimizes the count calculation, while collect(Collectors.counting()) does not.  

And that is the difference. If you need just the result, use count(). But if you would like to do something else with each element during the counting, use collect(Collectors.counting())

Please, notice that the count() behavior changes as soon as another intermediate operation is added that does affect the stream. For example:

   
  long c5 = Stream.of("cat", "fish", "dog")
        .limit(1)
        .peek(s -> System.out.print(s))    //prints: cat
        .count();
  System.out.print(c5);                    //prints: 1
  

In the above examples, we have added limit(1) operation. Now the peek() operation is not skipped.

And it does not matter, where relative to peek() position in the stream pipeline another operation is added:  

  
  long c7 = Stream.of("cat", "fish", "dog")
        .peek(s -> System.out.print(s))    //prints: cat
        .limit(1)
        .count();
  System.out.print(c7);                    //prints: 1

You can also use peek() to change a property of some object:

  
  class Counter{
    long count;
    public void Count(){}
    public void increment(){ this.count++; }
    public long getCount(){ return this.count; }
  }
  Counter counter1 = new Counter();
  long c9 = Stream.of("cat", "fish", "dog")
        .peek(s -> counter1.increment())    //not called
        .count();
  System.out.print(c9);                     //prints:3
  System.out.print(counter1.getCount());    //prints: 0

  Counter counter2 = new Counter();
  long c10 = Stream.of("cat", "fish", "dog")
        .peek(s -> counter2.increment())    //prints: cat
        .collect(Collectors.counting());
  System.out.print(c10);                    //prints: 3
  System.out.print(counter2.getCount());    //prints: 3
   

You can use the result of the object property change even before the stream processing is finished (using another thread, for example), which especially useful in the case the stream is infinite.

In the next post, we will continue discussing the collect() operation and demonstrate usage of the ready-to-use Collectors.joining() collector that that creates a new String object by joining stream elements.

See other posts on Java 8 streams and posts on other topics.
You can also use navigation pages for Java stream related blogs:
— Java 8 streams blog titles
— Create stream
— Stream operations
— Stream operation collect()
The source code of all the code examples is here in GitHub

, ,

Send your comments using the link Contact or in response to my newsletter.
If you do not receive the newsletter, subscribe via link Subscribe under Contact.

Powered by WordPress. Designed by Woo Themes