Guide to Java 8 Collectors: averagingDouble(), averagingLong() and averagingInt()

Introduction

A stream represents a sequence of elements and supports different kinds of operations that lead to the desired result. The source of a stream is usually a Collection or an Array, from which data is streamed from.

Streams differ from collections in several ways; most notably in that the streams are not a data structure that stores elements. They're functional in nature, and it's worth noting that operations on a stream produce a result and typically return another stream, but do not modify its source.

To "solidify" the changes, you collect the elements of a stream back into a Collection.

The mathematical operation of finding an arithmetic mean is one we use fairly frequently, and there are many ways we go about performing it.

We'll be doing exactly that in this guide - we'll take a look at how to get a mean/average value for different numerical types within Java through built-in methods within the Collectors class.

Note: It's worth noting that you can average the elements themselves, if they're numerical, or reduce them to a numerical representation and then average the reductions, if they aren't.

Collectors and Stream.collect()

Collectors represent implementations of the Collector interface, which implements various useful reduction operations, such as accumulating elements into collections, summarizing elements based on a specific parameter, etc.

All predefined implementations can be found within the Collectors class.

You can also very easily implement your own collector and use it instead of the predefined ones, though - you can get pretty far with the built-in collectors, as they cover the vast majority of cases in which you might want to use them.

To be able to use the class in our code we need to import it:

import static java.util.stream.Collectors.*;

Stream.collect() performs a mutable reduction operation on the elements of the stream.

A mutable reduction operation collects input elements into a mutable container, such as a Collection, as it processes the elements of the stream.

Definition of the averaging_() Collectors

The Collectors class has a variety of useful functions, and it so happens that it contains a few that allow us to find a mean value of input elements.

There's a total of three of them: Collectors.averagingInt(), Collectors.averagingDouble() and Collectors.averagingLong().

Let's start off by taking a look at the method signatures:

public static <T> Collector<T,?,Double> averagingInt(ToIntFunction<? super T> mapper)

public static <T> Collector<T,?,Double> averagingDouble(ToDoubleFunction<? super T> mapper)

public static <T> Collector<T,?,Double> averagingLong(ToLongFunction<? super T> mapper)

Note: The generic T in the method signatures represents the type of the input elements we're working with.

The ToIntFunction, ToDoubleFunction and ToLongFunction from java.util.function enable us to perform conversions (reductions) from object types to their primitive int, double or long fields. Let's define a Student class that we can reduce to a numerical field:

public class Student {
    private String name;
    private Double grade;
    private Integer age;
    private Long examsTaken;

   // Constructor, getters and setters

Let's also instantiate our students in a List:

List<Student> students = Arrays.asList(
    new Student("John", 7.56, 21, 17L),
    new Student("Jane", 8.31, 19, 9L),
    new Student("Michael", 9.17, 20, 14L),
    new Student("Danielle", 9.17, 21, 23L),
    new Student("Joe", 8.92, 22, 25L)
);

Besides custom objects - we'll also take a look at how we can use the averaging collectors on primitive data types - that is Lists consisted of only numerical elements.

Collectors.averagingInt()

This method returns a Collector that produces the arithmetic mean of an integer-valued function applied to the input elements. In the case of no elements being present, the result is 0.

Since all of the averaging methods are pretty straightforward we'll go straight to an example.

The first of the two examples will be using a simple List consisted of Integers. Let's say we have the following:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Double average = numbers.stream().collect(Collectors.averagingInt(Integer::intValue));
System.out.println(average);

We apply the .stream() method to create a stream of Integer objects, after which we use the previously discussed .collect() method to collect the stream with the averagingInt() collector.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Since the values are already integers, get the intValue through a method reference, effectively performing a 1-to-1 mapping as our ToIntFunction, as there's no conversion required:

3.0

Next on, in our Student class, the only integer-valued field is age. In the following example we'll be calculating the average age of all of our students:

Double averageAge = students.stream().collect(Collectors.averagingInt(Student::getAge));
System.out.println("Average student age is: " + averageAge);

The inner workings when using a field from a user-defined class is the same, the only difference is that we can't average the Student instances, so we reduce them to their age. The ToIntFunction in this case is a method reference to the getAge() method, and through it, we reduce the Student to their age.

This process stays the same in both of our upcoming methods as well, the only thing that'll change is which method we're referencing for the conversion functions. The output of our code snippet is:

Average student age is: 20.6

Collectors.averagingDouble()

This method returns a Collector that produces the arithmetic mean of an double-valued function applied to the input elements. In the case of no elements being present, the result is 0.

The Collectors.averagingDouble() differs a tiny bit from averagingInt() given the fact that it works with doubles. The average that's being returned can vary depending on the order in which values are processed, due to the accumulated rounding errors. Values sorted by increasing order tend to produce more accurate results.

If any value is NaN or the sum at any point is NaN - the mean will also be NaN. Additionally, the double format can be represented with all consecutive integers in the range from -253 to 253.

Let's calculate the average value of a list of doubles:

List<Double> numbers = Arrays.asList(3.0, 8.0, 4.0, 11.0);
Double average = numbers.stream().collect(Collectors.averagingDouble(Double::doubleValue));
System.out.println(average);

This results in:

6.5

Now, let's apply the method to our Student class. The grade field is a double, so we'll use a method reference to that field's getter as the ToDoubleFunction in the averagingDouble() call:

Double averageGrade = students.stream().collect(Collectors.averagingDouble(Student::getGrade));
System.out.println("Average student grade is: " + averageGrade);

Running this gives us the following output:

Average student grade is: 8.62

Collectors.averagingLong()

The last of the averaging methods is Collectors.averagingLong(). This method as well as the previous two, returns a Collector that produces the arithmetic mean of a long-valued function applied to the input elements. If there are no elements average, 0 is returned.

As with the previous two, we can easily average a list of long values:

List<Long> numbers = Arrays.asList(10L, 15L, 1L, 3L, 7L);
Double average = numbers.stream().collect(Collectors.averagingDouble(Long::longValue));
System.out.println(average);

This results in:

7.2

Finally, our Student class has a Long-valued examsTaken field, for which we can calculate the mean of. We'll use a method reference to the getExamsTaken() method as the ToLongFunction:

Double averageExamsTaken = students.stream().
    collect(Collectors.averagingLong(Student::getExamsTaken));
System.out.println("Average exam taken per student is: " + averageExamsTaken);

Running this outputs:

Average exam taken per student is: 17.6

Conclusion

Many a times we have a need for computing averages of multiple values, and using the provided averaging_() methods of the Collectors class is one of the easiest and most efficient ways to do this in Java.

In this guide, we've gone through all three of them available within the aforementioned class, explained how each of them works on an example of a user-defined class and showed how we can use them in code to achieve the results we were aiming for.

Last Updated: November 16th, 2021
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms