Java streams 27. Collect 3. Collectors summing, averaging, and summarizing.

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.summingInt(), Collectors.summingLong(), Collectors.summingDouble()

The API of factory methods that produce these collectors look similar:

Collector<T,?,Integer> summingInt(ToIntFunction<T> mapper) – returns a Collector that produces the sum of a integer-valued function applied to the input elements;

Collector<T,?,Long> summingLong(ToLongFunction<T> mapper) – returns a Collector that produces the sum of a long-valued function applied to the input elements;

— Collector<T,?,Double> summingDouble(ToDoubleFunction<T> mapper) – returns a Collector that produces the sum of a double-valued function applied to the input elements.

The following is an example of the Collectors.summingInt() usage: 

  
  Integer sum = Stream.of(1, 2, 3, 4, 5)
    .collect(Collectors.summingInt(Integer::intValue));
  System.out.print(sum);    //prints: 15
   

Alternatively, we can convert the stream to IntStream (using mapToInt() intermediate operation) and get the sum of the int-values as following :

  
  Integer sum = Stream.of(1, 2, 3, 4, 5)
        .mapToInt(Integer::intValue)
        .sum();
  System.out.print(sum);     //prints: 15
  

There is no functional difference between these two examples. The second example, though, reads more natural – from left to right. Consider the next code snippets:

  
  Integer sum = Stream.of("abc", "a", "xy")
        .collect(Collectors.summingInt(s -> s.length()));
  System.out.print(sum);        //prints: 6

  Integer sum = Stream.of("abc", "a", "xy")
        .mapToInt(s -> s.length())
        .sum();
  System.out.print(sum);        //prints: 6
  

It takes a bit longer to understand the first from the above two implementations than the second one, where you can read the functional flow the same way you used to see it in the traditional sequential programming: first, convert String value to int, then create IntStream object, and call the terminal sum() operation on it. 

Another reason you may prefer using mapToInt() and sum() over collector is the ability to add other intermediate operations before the terminal one. For example, you may want to send the newly created int-values somewhere else or change them conditionally before calculating the sum. No doubt, you can do it inside the function passed into the summingInt() as a parameter, too, but, again, the processing flow would read more natural if included in the stream before the terminal operation.   

By contrast, the collector usage allows you to combine it with other collectors, if you need it. We will show how to do it in one of the future posts of this series. Here is just one example:

  
  Integer sum = Stream.of("abc", "a", "xy")
     .collect(Collectors.collectingAndThen(Collectors
            .summingInt(s -> s.length()), i -> i * i));
  System.out.print(sum);        //prints: 36
  

In this example, we used collectingAndThen() method to add another – finishing – function that multiplies the result by itself. 

The other two “summing” collectors can be used the same way:

   
  Long sum = Stream.of(1L, 2L, 3L, 4L, 5L)
       .collect(Collectors
                     .summingLong(Long::longValue));
  System.out.print(sum);       //prints: 15

  Double sum = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
       .collect(Collectors
                .summingDouble(Double::doubleValue));
  System.out.print(sum);       //prints: 15.0
  

Alternatively, as in the case of IntStream, the above examples can be implemented using LongStream and DoubleStream, correspondingly:

  
  Long sum = Stream.of(1L, 2L, 3L, 4L, 5L)
        .mapToLong(Long::longValue)
        .sum();
  System.out.print(sum);      //prints: 15

  Double sum = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
        .mapToDouble(Double::doubleValue)
        .sum();
  System.out.print(sum);      //prints: 15.0
  


Collectors.averagingInt(), Collectors.averagingLong(), Collectors.averagingDouble()

The API of factory methods that produce these collectors is as follows:

Collector<T,?,Double> averagingInt(ToIntFunction<T> mapper) – returns a Collector that produces the arithmetic mean of an integer-valued function applied to the input elements;

Collector<T,?,Double> averagingLong(ToLongFunction<T> mapper) – returns a Collector that produces the arithmetic mean of a long-valued function applied to the input elements;

— Collector<T,?,Double> averagingDouble(ToDoubleFunction<T> mapper) – returns a Collector that produces the arithmetic mean of a double-valued function applied to the input elements.

The following are examples of these collectors usage: 

  
  
  Double ave = Stream.of(1, 2, 3, 4, 5)
        .collect(Collectors
                     .averagingInt(Integer::intValue));
  System.out.print(ave);                  //prints: 3.0

  Double ave = Stream.of(1L, 2L, 3L, 4L, 5L)
        .collect(Collectors
                     .averagingLong(Long::longValue));
  System.out.print(ave);                 //prints: 3.0

  Double ave = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
        .collect(Collectors
                .averagingDouble(Double::doubleValue));
  System.out.print(ave);                  //prints: 3.0
  

The alternative implementation, as in the case of “summing” collectors, looks as follows:

  
  Double ave = Stream.of(1, 2, 3, 4, 5)
        .mapToInt(Integer::intValue)
        .average()
        .getAsDouble();
  System.out.print(ave);         //prints: 3.0

  Double ave = Stream.of(1L, 2L, 3L, 4L, 5L)
        .mapToLong(Long::longValue)
        .average()
        .getAsDouble();
  System.out.print(ave);         //prints: 3.0

  Double ave = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
        .mapToDouble(Double::doubleValue)
        .average()
        .getAsDouble();
  System.out.print(ave);         //prints: 3.0
  

One small difference is that the operation average() returns DoubleOptional, so the getAsDouble() method has to be used to extract the result. 



Collectors.summarizingInt(), Collectors.summarizingLong(), Collectors.summarizingDouble()

The API of factory methods that produce these collectors is as follows (see online doc):

Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<T> mapper) – returns a Collector which applies an int-producing mapping function to each input element, and returns summary statistics for the resulting values;

Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<T> mapper) – returns a Collector which applies an long-producing mapping function to each input element, and returns summary statistics for the resulting values;

Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<T> mapper) – returns a Collector which applies an double-producing mapping function to each input element, and returns summary statistics for the resulting values.

The following are examples of these collectors usage: 

  
  IntSummaryStatistics smr = Stream.of(1, 2, 3, 4, 5)
    .collect(Collectors.summarizingInt(Integer::intValue));
  System.out.println(smr.getCount());         //prints: 5
  System.out.println(smr.getMin());           //prints: 1
  System.out.println(smr.getMax());           //prints: 5
  System.out.println(smr.getAverage());       //prints: 3.0
  System.out.println(smr.getSum());           //prints: 15

  LongSummaryStatistics smr = Stream.of(1L, 2L, 3L, 4L, 5L)
     .collect(Collectors.summarizingLong(Long::longValue));

  DoubleSummaryStatistics smr = 
                        Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
     .collect(Collectors
                 .summarizingDouble(Double::doubleValue));
  System.out.println(smr.getCount());         //prints: 5
  System.out.println(smr.getMin());           //prints: 1.0
  System.out.println(smr.getMax());           //prints: 5.0
  System.out.println(smr.getAverage());       //prints: 3.0
  System.out.println(smr.getSum());           //prints: 15.0
      

As you can see, the summarizing collector produces an object that contains a count of stream elements, min, max, average, and sum (for the brevity, we have omitted demonstration of LongSummaryStatistics, since it contains the same – only long – values as IntSummaryStatistics object).

The alternative implementation, as in the case of “summing” collectors, looks as follows:

  
  IntSummaryStatistics smr = Stream.of(1, 2, 3, 4, 5)
        .mapToInt(Integer::intValue)
        .summaryStatistics();
  System.out.println(smr.getCount());         //prints: 5
  System.out.println(smr.getMin());           //prints: 1
  System.out.println(smr.getMax());           //prints: 5
  System.out.println(smr.getAverage());       //prints: 3.0
  System.out.println(smr.getSum());           //prints: 15

  LongSummaryStatistics smr = Stream.of(1L, 2L, 3L, 4L, 5L)
        .mapToLong(Long::longValue)
        .summaryStatistics();

  DoubleSummaryStatistics smrDouble1 = 
                     Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
        .mapToDouble(Double::doubleValue)
        .summaryStatistics();
  System.out.println(smr.getCount());        //prints: 5
  System.out.println(smr.getMin());          //prints: 1.0
  System.out.println(smr.getMax());          //prints: 5.0
  System.out.println(smr.getAverage());      //prints: 3.0
  System.out.println(smr.getSum());          //prints: 15.0
  

Which of the above implementations to chose is pretty much depends on your coding style:

— whether you like to the functional flow being read from left to right, or  

— whether you need to add more intermediate operations and you prefer dot-flowing style or having a coding block as the function passed in the summarizing operation.

In the next post, we will continue discussing the collect() operation and show examples of its usage. We will demonstrate how it can be used to count stream elements using Collectors.counting() collector. It does a bit more than the Stream operator count().

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