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:
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:
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:
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.