Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Use Java Stream Collectors

DZone's Guide to

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.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Java 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  Employeeas:

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.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
java ,collectors ,summingint ,statistical values ,tutorial ,stream

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}