DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Using Java Stream Gatherers To Improve Stateful Operations
  • Thread-Safety Pitfalls in XML Processing
  • Java Stream API: 3 Things Every Developer Should Know About
  • Understanding Lazy Evaluation in Java Streams

Trending

  • Memory Leak Due to Time-Taking finalize() Method
  • System Coexistence: Bridging Legacy and Modern Architecture
  • Introduction to Retrieval Augmented Generation (RAG)
  • Software Delivery at Scale: Centralized Jenkins Pipeline for Optimal Efficiency
  1. DZone
  2. Coding
  3. Java
  4. How to Use Java Stream Collectors

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.

By 
Yogen Rai user avatar
Yogen Rai
·
Jul. 20, 18 · Tutorial
Likes (38)
Comment
Save
Tweet
Share
40.8K Views

Join the DZone community and get the full member experience.

Join For Free

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.

Stream (computing) Java (programming language)

Published at DZone with permission of Yogen Rai, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Using Java Stream Gatherers To Improve Stateful Operations
  • Thread-Safety Pitfalls in XML Processing
  • Java Stream API: 3 Things Every Developer Should Know About
  • Understanding Lazy Evaluation in Java Streams

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: