Collecting & Aggregating StreamsJ8 Home « Collecting & Aggregating Streams

In the Stream Collectors lesson we looked in detail at the Collector<T,A,R> interface as this is used by the overloaded collect() method of the Stream<E> interface that takes a Collector object as a parameter. We also investigated the Collectors class which has useful implementations of Collector<T,A,R>, including ones to create collections, find aggregates and group and/or partition a stream.

In this first lesson on using stream collectors we look at collection creation and aggregation of our streams.

Grouping & Partitioning our streams is discussed in the Grouping & Partitioning Streams lesson.

Collections From Streams Top

The Collectors class has static methods for collecting a stream into a list, set or map and even into a collection implementation.

Lets see how these methods work! We will be using the Employee class we created in the Introducing Streams lesson to test against for this purpose.

Following is a TestStreamToCollection class to demonstrate collecting a stream using the collect() method of the Stream<E> interface in conjunction with static methods of the Collectors class.


package info.java8;

import java.util.*;

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

/* Create collections from streams */
public class TestStreamToCollection {

    public static void main(String[] args) {

        // Collect to Map
        System.out.println("+++ Collecting to Map +++");
        Map<Double, String> nameAndSalary = Employee.listOfStaff().stream()
                .collect(toMap(Employee::getSalary, Employee::getName));
        nameAndSalary.forEach((key, value) -> System.out.println(key + ":" + value));

        // Collect to List
        System.out.println("+++ Collecting to List +++");
        List<Employee> employeeList = Employee.listOfStaff().stream()
                .filter(e -> e.getAge() > 45)
                .collect(toList());
        for (Employee e : employeeList) {
            System.out.println(e);
        }

        // Collect to Set
        System.out.println("+++ Collecting to Set +++");
        Set<Double> salaries = Employee.listOfStaff().stream()
                .filter(e -> e.getAge() > 45)
                .map(Employee::getSalary)
                .collect(toSet());
        for (Double d : salaries) {
            System.out.println(d);
        }

        // Collect to implemented Set
        System.out.println("+++ Collecting to implemented Set +++");
        Set<Employee> employeeSortedName = Employee.listOfStaff().stream()
                .filter(e -> e.getGender().toString().
                        equals(Employee.Sex.FEMALE.toString()))
                .sorted(Comparator.comparing(Employee::getName))
                .collect(toCollection(LinkedHashSet::new));
        for (Employee e : employeeSortedName) {
            System.out.println(e);
        }
    }
}

Building and running the TestStreamToCollection class produces the following output:

Run TestStreamToCollection class
Screenshot 1. Running the TestStreamToCollection class.

Something went wrong! Looking at the stacktrace we're getting an IllegalStateException because of a duplicate key on Dorothy (our value). If we look in the Employee class for Dorothy and check the salary (our key) we can see that Dorothy and Bert have the same salary which is causing the problem with the duplicate key. Luckily for us there is a toMap override which take a third parameter where we can declare what to do with duplicates.

Following is a code snippet for the revised code using the toMap override. We are choosing a new value for our key, so Dorothy will get replaced with Bert as the value.


// Collect to Map
System.out.println("+++ Collecting to Map +++");
Map<Double, String> nameAndSalary = Employee.listOfStaff().stream()
        .collect(toMap(Employee::getSalary, Employee::getName,
                (currentValue, newValue) -> newValue)); // Choose 
nameAndSalary.forEach((key, value) -> System.out.println(key + ":" + value));

Rebuilding and running the TestStreamToCollection class produces the following output:

Run TestStreamToCollection class 2
Screenshot 2. Rerunning the TestStreamToCollection class.

Ok, everything works fine now, lets go though the code and see what's new!

We do a static import of the Collectors class to save prefixing this for every method and to make the code more readable.

There are methods to convert to maps, list and sets using the toMap(), toList() and toSet() static methods of the Collectors class which we use as parameter entry to the collect() method.

We use the sorted() method of the Stream<E> interface which returns a stream consisting of the elements of this stream, sorted according to natural order, for the first time.

We also use the toCollection() static methods of the Collectors class to pass our stream to the LinkedHashSet implementation of Set.

Aggregates From Streams Top

Lets continue our investigation of stream collectors by looking at some aggregation methods of the Collectors class we can use as parameters to our collect() method.

Once again we will be using the Employee class we created in the Introducing Streams lesson to test against for this purpose.

Following is a TestStreamToAggregates class to demonstrate collecting a stream using the collect() method of the Stream<E> interface in conjunction with static methods of the Collectors class.


package info.java8;

import java.util.Comparator;
import java.util.DoubleSummaryStatistics;
import java.util.Optional;

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

/* Create aggregates from streams */
public class TestStreamToAggregates {

    public static void main(String[] args) {

        Comparator<Employee> employeeSalaryComparator  =
                Comparator.comparing(Employee::getSalary);

        /* Top salary */
        Optional<Employee> topSalary = Employee.listOfStaff().stream()
                .collect(maxBy(employeeSalaryComparator));
        System.out.println("Top salary is: " + topSalary);

        /* Bottom salary */
        Optional<Employee> bottomSalary = Employee.listOfStaff().stream()
                .collect(minBy(employeeSalaryComparator));
        System.out.println("Bottom salary is: " + bottomSalary);

        /* Average salary */
        Double averageSalary =
                Employee.listOfStaff().stream()
                        .collect(averagingDouble(Employee::getSalary));
        System.out.println("Average Salary is: " + averageSalary);

        /* Total salary */
        Double totalSalaries =
                Employee.listOfStaff().stream()
                        .collect(summingDouble(Employee::getSalary));
        System.out.println("Total Salaries: " + totalSalaries);

        /* Summarize salary */
        DoubleSummaryStatistics salaryStatistics =
                Employee.listOfStaff().stream()
                        .collect(summarizingDouble(Employee::getSalary));
        System.out.println("Salary Statistics are: " + salaryStatistics);

        /* Join and delimit names */
        String employeeNames = Employee.listOfStaff().stream()
                .map(Employee::getName)
                .sorted()
                .collect(joining(", "));
        System.out.println("Employee names are: " + employeeNames);
    }
}

Building and running the TestStreamToAggregates class produces the following output:

Run TestStreamToAggregates class
Screenshot 3. Running the TestStreamToAggregates class.

From the screenshot we can see everything works so lets go though the code and see what static methods of the Collectors class we've used with the collect() method of the Stream<E> interface.

We do a static import of the Collectors class to save prefixing this for every method and to make the code more readable.

We create a comparator for field salary so we can use collect() with the maxBy and minBy static methods of the Collectors class to get these aggregates.

Next we find average and total salaries using the averagingDouble and summingDouble static methods of the Collectors class respectively.

We then print off statistics for salaries by returning and printing a DoubleSummaryStatistics when using collect() with the summarizingDouble static method of the Collectors class. There are also versions for the int and long primitives.

Finally we print of all sorted employess names, delimited by a comma, using collect() with the joining static method of the Collectors class.

Once again we can see how lightweight and expressive the code is in telling the what to do not how to do it.

Related Quiz

Streams Quiz 9 - Stream Collectors Quiz

Lesson 9 Complete

In this lesson we stream collectors and some of the methods used to create collections and extract aggreagates from streams.

What's Next?

In the next lesson we look at utilising our stream data by arranging it into groups.