How to Use Java Stream Collectors
Want to learn more about using Java Stream collectors? Check out this post on collectors and how to use them.
Join the DZone community and get the full member experience.
Join For FreeJava 8 has introduced a new abstraction called stream, letting us process data in a declarative way. Furthermore, streams can leverage multi-core architectures without you having to write a single line of multithread code.
Collectors are a class an implementations of Collector
that implement various useful reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc.
Using Collectors
To demonstrate the usage of stream Collectors
, let me define a class to hold my data as:
class Employee {
private String empId;
private String name;
private Double salary;
private String department;
public Employee(String empId, String name, Double salary, String department) {
this.empId = empId;
this.name = name;
this.salary = salary;
this.department = department;
}
// getters and toString
}
So, let me create a list of Employee
as:
Employee john = new Employee("E123", "John Nhoj", 200.99, "IT");
Employee south = new Employee("E223", "South Htuos", 299.99, "Sales");
Employee reet = new Employee("E133", "Reet Teer", 300.99, "IT");
Employee prateema = new Employee("E143", "Prateema Rai", 300.99, "Benefits");
Employee yogen = new Employee("E323", "Yogen Rai", 200.99, "Sales");
List<Employee> employees = Arrays.asList(john, south, reet, prateema, yogen);
Calculating Statistical Values
Finding Average Salary
Double averageSalary = employees.stream().collect(averagingDouble(Employee::getSalary));
// 260.79
Similarly, there are averagingInt(ToIntFunction<? super T> mapper)and averagingLong(ToLongFunction<? super T> mapper) to find the average values for Integer
and Long
types.
Finding Total Salary
Double totalSalary = employees.stream().collect(summingDouble(Employee::getSalary));
// 1303.95
summingInt(ToIntFunction<? super T> mapper) and summingLong(ToLongFunction<? super T> mapper) are available for summing Integer
and Long
types.
Finding Max Salary
Double maxSalary = employees.stream().collect(collectingAndThen(maxBy(comparingDouble(Employee::getSalary)), emp -> emp.get().getSalary()));
// 300.99
collectingAndThen
function has declaration of:
Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher)
Function finisher
can be used to format the final result of the Collector
output as:
String avgSalary = employees.stream()
.collect(collectingAndThen(averagingDouble(Employee::getSalary), new DecimalFormat("'$'0.000")::format));
// $260.790
Calculating Statistics in One Shot
DoubleSummaryStatistics statistics = employees.stream().collect(summarizingDouble(Employee::getSalary));
System.out.println("Average: " + statistics.getAverage() + ", Total: " + statistics.getSum() + ", Max: " + statistics.getMax() + ", Min: "+ statistics.getMin());
// Average: 260.79, Total: 1303.95, Max: 300.99, Min: 200.99
Similarly, summarizingInt(ToIntFunction<? super T> mapper)and summarizingLong(ToLongFunction<? super T> mapper) are available for Integer
and Long
types.
Mapping and Joining Stream
Mapping Only Employee Names
List<String> employeeNames = employees.stream().collect(mapping(Employee::getName, toList()));
// [John Nhoj, South Htuos, Reet Teer, Prateema Rai, Yogen Rai]
Joining Employee Names
String employeeNamesStr = employees.stream().map(Employee::getName).collect(joining(","));
// John Nhoj,South Htuos,Reet Teer,Prateema Rai,Yogen Rai
The joining()
function has overloaded version to take prefix as suffix as:
Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
So, if you want to collect employee names in a specific format, then, you can do:
String employeeNamesStr = employees.stream().map(Employee::getName).collect(joining(", ", "Employees = {", "}"));
// Employees = {John Nhoj, South Htuos, Reet Teer, Prateema Rai, Yogen Rai}
Grouping Elements
Grouping employees by Department
groupingBy()
takes classifier Function
as:
Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)
So, grouping of employees by department is:
Map<String, List<Employee>> deptEmps = employees.stream().collect(groupingBy(Employee::getDepartment));
// {Sales=[{empId='E223', name='South Htuos', salary=299.99, department='Sales'}, {empId='E323', name='Yogen Rai', salary=200.99, department='Sales'}], Benefits=[{empId='E143', name='Prateema Rai', salary=300.99, department='Benefits'}], IT=[{empId='E123', name='John Nhoj', salary=200.99, department='IT'}, {empId='E133', name='Reet Teer', salary=300.99, department='IT'}]}
Counting Employees per Department
There is an overloaded version of groupingBy()
as:
Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier,Collector<? super T,A,D> downstream)
So, counting employees per department would be:
Map<String, Long> deptEmpsCount = employees.stream().collect(groupingBy(Employee::getDepartment, counting()));
// {Sales=2, Benefits=1, IT=2}
Calculating Average Salary per Department With Sorted Department Name
Another overload method of groupingBy()
is:
Collector<T,?,M> groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
TreeMap
can be used to groupBy
department name sorted as:
Map<String, Double> averageSalaryDeptSorted = employees.stream().collect(groupingBy(Employee::getDepartment, TreeMap::new, averagingDouble(Employee::getSalary)));
// {Benefits=300.99, IT=250.99, Sales=250.49}
There is a ConcurrentHashMap
version of groupBy(),
leveraging multi-core architectures.
Map<String, Long> deptEmpCount = employees.stream().collect(groupingByConcurrent(Employee::getDepartment, counting()));
// {Sales=2, IT=2, Benefits=1}
Partitioning Elements
partitioningBy()
takes a predicate to partion the result into true for meeting the predicate criterion and false for not as:
Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)
Finding employees with a salary greater than the average salary is:
Map<Boolean, List<Employee>> portionedEmployees = employees.stream().collect(partitioningBy(e -> e.getSalary() > averageSalary));
// {false=[{empId='E123', name='John Nhoj', salary=200.99, department='IT'}, {empId='E323', name='Yogen Rai', salary=200.99, department='Sales'}],
true=[{empId='E223', name='South Htuos', salary=299.99, department='Sales'}, {empId='E133', name='Reet Teer', salary=300.99, department='IT'}, {empId='E143', name='Prateema Rai', salary=300.99, department='Benefits'}]}
You can use overloaded version of this method to filter the result as:
Collector<T,?,Map<Boolean,D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream)
Conclusion
The Collectors class has many utility functions to operate over the stream and extract the result meaningfully.
All the source code for the example above are available on GitHub.
Published at DZone with permission of Yogen Rai, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments