June 29, 2022

Java Stream - Collectors Class And collect() Method

When using Java streams, most of the time you will have a Collection as a source for the stream but you can also do the opposite i.e. obtain a Collection from a Stream. To do that you can use collect() method in the Java Stream API. Here note that collect() method performs a mutable reduction operation on the elements of this stream which returns a mutable result container. This mutable result container can be a Collection class like ArrayList, HashSet or a StringBuilder etc.

collect() method in Java Stream

collect() method is a terminal operation and there are two overloaded collect() methods.

1- <R,A> R collect(Collector<? super T,A,R> collector)- Performs a mutable reduction operation on the elements of this stream using a Collector.

In the method type parameters are as-

T- The type of input elements to the reduction operation

A - the intermediate accumulation type of the Collector

R - the type of the result

2-<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)- Performs a mutable reduction operation on the elements of this stream.

Parameters of the method are as follows-

R- the type of the mutable result container

supplier- a function that creates a new mutable result container. It is an instance of Supplier functional interface.

accumulator- an associative, non-interfering, stateless function that must fold an element into a result container. It is an instance of BiConsumer functional interface.

combiner- an associative, non-interfering, stateless function that accepts two partial result containers and merges them, which must be compatible with the accumulator function. It is an instance of BiConsumer functional interface.

Collectors class in Java Stream

In the first collect() method you can see that argument is of type Collector which is an interface in java.util.stream package and defines many methods.

Rather than implementing those methods yourself you can use Collectors class which is an implementation of Collector and provides many utility reduction methods such as accumulating elements into collections, summarizing elements according to various criteria, etc.

Java Collectors example

In this section we’ll see some examples of using predefined Collectors along with collect() method.

Most of the examples use objects of Employee class which has fields name, dept, salary.

public class Employee {
  private String name;
  private String dept;
  private int salary;

  Employee(String name, String dept, int salary){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getSalary() {
    return salary;
  }
  public void setSalary(int salary) {
    this.salary = salary;
  }
  public String getDept() {
    return dept;
  }
  public void setDept(String dept) {
    this.dept = dept;
  }
}
1. Collecting into a list – Collectors.toList() Collecting all the employee names to a List.
List<Employee> employeeList = new ArrayList<>(); 

employeeList.add(new Employee("Jack", "Finance", 5500)); 
employeeList.add(new Employee("Lisa", "Finance", 5600)); 
employeeList.add(new Employee("Scott", "Finance", 7000));
employeeList.add(new Employee("Nikita", "IT", 4500));
employeeList.add(new Employee("Tony", "IT", 8000)); 

List<String> names = employeeList.stream()
                                 .map(e -> e.getName())
                                 .collect(Collectors.toList());

names.forEach(System.out::println);
Output
Jack
Lisa
Scott
Nikita
Tony

2. Collecting into a Set – Collectors.toSet()

Set<String> names = employeeList.stream()
                                .map(Employee::getName)
                                .collect(Collectors.toSet());

names.forEach(System.out::println);
Output
Tony
Nikita
Jack
Lisa
Scott

3. Collecting into a TreeSet – Collectors.toCollection()

Collecting all the employee names into a TreeSet to get names in order.
Set<String> names = employeeList.stream()
                                .map(Employee::getName)
                                .collect(Collectors.toCollection(TreeSet::new));

names.forEach(System.out::println);
Output
Jack
Lisa
Nikita
Scott
Tony

4. Collecting into a Map – Collectors.toMap()

To accumulate elements into a Map using toMap() method you need to provide two functions-

keyMapper- This function is used to get keys by applying function to the input elements.

valueMapper- This function is used to get values by applying function to the input elements.

Map<String, Integer> names = employeeList.stream()
                                         .collect(Collectors.toMap(Employee::getName, Employee::getSalary));
							
names.entrySet().forEach(es->{System.out.println("Key- " + es.getKey() + " Value- " +  es.getValue());});
Output
Key- Tony Value- 8000
Key- Nikita Value- 4500
Key- Jack Value- 5500
Key- Lisa Value- 5600
Key- Scott Value- 7000

5. Convert elements to strings and concatenate them- Collectors.joining

If you want to display employee names as a comma separated string.

String names = employeeList.stream()
                           .map(Employee::getName)
                           .collect(Collectors.joining(", "));

System.out.println(names);
Output
Jack, Lisa, Scott, Nikita, Tony

6. Computing sum- Collectors.summingInt()

Sum of salaries paid to employees.

int totalSalary = employeeList.stream()
                              .collect(Collectors.summingInt(Employee::getSalary));

System.out.println("Total salary paid to employees per month- " + totalSalary);
Output
Total salary paid to employees per month- 30600

7. Grouping by a field- Collectors.groupingBy()

If you want to group the employees by department, return value is a Map.

Map<String, List<Employee>> names = employeeList.stream()
                                                            .collect(Collectors.groupingBy(Employee::getDept));

names.entrySet().forEach(es->{System.out.println("Key- " + es.getKey());
                        System.out.println("Values");
                        es.getValue().forEach(e->System.out.println(e.getName()));});
Output
Key- Finance
Values
Jack
Lisa
Scott
Key- IT
Values
Nikita
Tony
8. Collectors.partitioningBy

Returns a Collector which partitions the input elements according to a Predicate, and organizes them into a Map<Boolean, List<T>>. The returned Map always contains mappings for both false and true keys. For true key those elements matching the given predicate are mapped and the elements not matching the given predicate are mapped under false key.

To partition employees into groups having salary >= 7000 and less than it.

Map<Boolean, List<Employee>> names = employeeList.stream()
                                                 .collect(Collectors.partitioningBy(e -> e.getSalary() >= 7000));
Output
Key- false
Values
Jack
Lisa
Nikita
Key- true
Values
Scott
Tony
9. Collectors.teeing

Returns a Collector that is a composite of two downstream collectors. Every element passed to the resulting collector is processed by both downstream collectors, then their results are merged using the specified merge function into the final result. This method is added in JDK 12.

Getting count and sum of elements in a List using Collectors.teeing function.

 List<Integer> listOfNumbers = Arrays.asList(11, 10, 9, 99, 98);
 List<String> list = listOfNumbers.stream().collect(Collectors.teeing(Collectors.counting(), Collectors.summingInt(n->Integer.valueOf(n.toString())), 
		 (a, s)->{List<String> l = new ArrayList<>();
		 		l.add(a.toString());
		 		l.add(s.toString());
		 		return l;}));
 list.forEach(System.out::println);
Output
5
227

10. Summary statistics methods in Collectors class

There are three methods summarizingInt, summarizingLong and summarizingDouble that returns summary statistics for the resulting values.

To get summary statistics about the salary of employees.

IntSummaryStatistics stats = employeeList.stream().collect(Collectors.summarizingInt(Employee::getSalary));
System.out.println("Sum of salaries - " + stats.getSum());
System.out.println("Average of salaries " + stats.getAverage());
System.out.println("Max salary " + stats.getMax());
System.out.println("Min salary " + stats.getMin());
Output
Sum of salaries - 30600
Average of salaries 6120.0
Max salary 8000
Min salary 4500

Using collect method with Combiner

This form of collect method requires three functions: a supplier function to construct new instances of the result container, an accumulator function to incorporate an input element into a result container, and a combining function to merge the contents of one result container into another.

For example collect the Integer representations of the elements in a stream into an ArrayList.

List<Integer> numbers = Stream.of(1, 2, 3, 4, 5).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
numbers.forEach(System.out::println);
Output
1
2
3
4
5

Getting all the employee names as a concatenated string where values are separated by comma.

String concat = employeeList.stream().map(Employee::getName).collect( () -> new StringJoiner(",", "", ""), StringJoiner::add, StringJoiner::merge).toString();					   
System.out.println("Employee Names- " + concat);
Output
Employee Names- Jack,Lisa,Scott,Nikita,Tony
Reference: https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/stream/Collectors.html

That's all for the topic Java Stream - Collectors Class And collect() Method. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment