Pages

Wednesday, December 22, 2021

Java 8 Collectors Examples In Depth

1. Overview


In this tutorial, We'll be learning to Java 8 Collectors API in-depth with all methods and example programs. Collectors is a public final class that extends the Object class.

Read this article completely with patience. You will definitely become a master in Java 8’s Collectors by end of this post.

Collectors class provides various useful reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc

Java 8 Collectors Examples In Depth




This has many methods that are very useful when working with Stream API.

Few methods: toList(), toMap(), toCollection(), joining(), summingInt(), groupingBy() and partitioningBy(), etc.


We will see the example programs on the below Collectors methods and how to use them.

Collectors API Methods:

  1. collect()
  2. toList()
  3. toSet()
  4. toUnmodifiableSet()
  5. toUnmodifiableList(()
  6. toCollection()
  7. toMap()
  8. toUnmodifiableMap()
  9. summingInt()
  10. averagingInt() / averagingLong() / averagingDouble()s
  11. counting()
  12. joining()
  13. groupingBy()
  14. partitioningBy()
  15. toConcurrentMap()
  16. filtering()
  17. flatMapping()
  18. maxBy()
  19. minBy()
  20. reducing()
  21. summarizingDouble() / summarizingInt() / summarizingLong()
  22. teeing()

Note: All methods in the Collectors class are static. So it is good to use static import.

If you are using many methods then use static import.
import static java.util.stream.Collectors.*;
If you are using only a few then use like this.
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.joining;
We will be using the below Employee class in this article.
class Employee {
private int id;
private String name;
private int age;
private String region;
private double sal;

public Employee(int id, String name, int age, String region, double sal) {
this.id = id;
this.name = name;
this.age = age;
this.region = region;
this.sal = sal;
}

// Standard setters and getters
}
Creating an Employee List.
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(100, "Sundar", 47, "North America", 450000));
employeeList.add(new Employee(200, "Pichai", 25, "North America", 50000));
employeeList.add(new Employee(300, "Larry", 30, "Asia", 450000));
employeeList.add(new Employee(400, "Page", 59, "Africa", 450000));

2. Java 8 Stream.collect() Example


Java 8's most powerful stream method is collect() method. Which is also called a Terminal method. This is part of Stream API.

It allows us to perform mutable fold operations (repackaging elements to some data structures and applying some additional logic, concatenating them, etc.) on data elements held in a Stream instance.

The strategy for this operation is provided via Collector interface implementation.

3. Collectors.toList() Example

toList() collector can be used for collecting all Stream elements into a List instance. 

Example to collect all employee names into List using toList() method.
List<String> namesList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println(namesList);

Output:
[Sundar, Pichai, Larry, Page]
But, there are no guarantees on the type, mutability, serializability, or thread-safety of the List returned.

If you need more control over what type of List should be returned then should use toCollection(Supplier) method.

4. Collectors.toSet() Example


toSet() collector is used for collecting all Stream elements into a Set instance

Example to collect all the regions into Set.
Set<String> regionSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toSet());
System.out.println(regionSet);
Output:
[Asia, Africa, North America]
But, there are no guarantees on the type, mutability, serializability, or thread-safety of the Set returned.

If you need more control over what type of Set should be returned then should use the toCollection(Supplier) method.

5. Collectors.toUnmodifiableSet() Example


This collects the elements into an unmodifiable set.

The set is created using the toSet() method can be modified. 
regionSet.add("hello");
System.out.println(regionSet);

Output:
[Asia, Africa, hello, North America]
toUnmodifiableSet() method works similar to the toSet() but this set can not be modified.
Set<Double> unmodifiableSet = employeeList.stream().map(e -> e.getSal()).collect(Collectors.toUnmodifiableSet());
System.out.println(unmodifiableSet);

Output:
[450000.0, 50000.0]

If we try to modify set then will throw UnsupportedOperationException.
unmodifiableSet.add(10983d);
Exception:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:72)

The returned collector does not allow null values. This will throw NullPointerException if it is presented with a null value.
employeeList.add(null);
Set<Employee> empSet = employeeList.stream().collect(Collectors.toUnmodifiableSet());

The above code will cause NullPointerException. The same will be in the case of the toSet() method.

6. Collectors.toUnmodifiableList(() Example


This is similar to the toList() but toUnmodifiableList will collect elements into an unmodifiable List.
List<Double> unmodifiableList = employeeList.stream().map(e -> e.getSal()).collect(Collectors.toUnmodifiableList());
System.out.println(unmodifiableList);

Output:
[450000.0, 50000.0, 450000.0, 450000.0]
This list holds duplicates, unlike Set.

If List has null value then it will throw java.lang.NullPointerException like toUnmodifiableSet.

7. Collectors.toCollection() Example


As you probably already noticed, when using toSet() and toList() collectors, you can’t make any assumptions of their implementations. 
If you want to use a custom implementation or LinkedList or TreeSet, you will need to use the toCollection collector with a provided collection of your choice.

Example to collect the names into LinkedList as opposed to default List implementation.
List<String> namesLinkedList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toCollection(LinkedList::new));
System.out.println(namesLinkedList);

Output:
[Sundar, Pichai, Larry, Page]
Another example to collect regions into TreeSet.
Set<String> regionTreeSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toCollection(TreeSet::new));
System.out.println(regionTreeSet);

Output:
[Africa, Asia, North America]
See the output is sorted because TreeSet sorts the values in it.

Note: This method does not work with immutable objects. In such type of cases, We must write custom Collector implementation or Use collectingAndThen().

8. Collectors.toMap() Example

toMap() Syntax:
public static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
Using toMap() method, A stream can be converted into a Map. But, this method needs two parameters.

keyMapper
valueMapper

These two are implementations of Function Functional Interface.
Functional Interface Function has a functional method R apply(T t) that accepts one argument and produces a result.

keyMapper will be used for extracting a Map key from a Stream element, and valueMapper will be used for extracting a value associated with a given key.

Now, We will create a map from a stream such that the map key is emp id and value is corresponding employee object.
keyMapper = (e) -> e.getId()
e refers to the Employee object and getting its id by calling getId() method.
valueMapper =  Function.identity() 
This method returns a function that always returns its input argument.
Function.identity() method does take one object as an argument and returns the same object with no change.
Map<Integer, Employee> empMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity()));
System.out.println(empMap);

Output:
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}

What happens if employeeList has duplicate employees with the same employee id?

Now adding a duplicate emp object with the same emp id but the different name "Larry Page".
employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Added a new emp object with emp id = 400.

Map<Integer, Employee> empDupMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity()));

Will throw the Runtime Exception as follow.
Exception in thread "main" java.lang.IllegalStateException: Duplicate key 400 (attempted merging values Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0] and Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0])
at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:133)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at com.java.w3schools.blog.java8.streams.Java8CollectorExamples.main(Java8CollectorExamples.java:76)

Note: Map does not check the duplicate objects but does not allow duplicate keys.

toMap() function takes 3rd argument as BinaryOperator Functional Interface which has a functional method R apply(T t, U u). This functional method takes two arguments. In our case, the first argument takes the original employee, Second argument takes the duplicate employee and returns the employee object.
Map<Integer, Employee> empDupMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);
Output:

Here BinaryOperator will be invoked when there the same key appears. And returning duplicate objects from BinaryOperator apply() method. That replaces the old employee with a new duplicate employee. See the below output.
{400=Employee [id=400, name=Larry Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
Observe the employee with id 400 has a new name "Larry Page". If we want to keep existing emp in the map and ignore the duplicate emp then we should return emp instead of sameEmp.

9. Collectors.toUnmodifiableMap() Example

Syntax:
public static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
public static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)
Returns a Collector that accumulates the input elements into an unmodifiable Map, whose keys and values are the result of applying the provided mapping functions to the input elements. 
Map<Integer, Employee> empUnmodifiedMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empUnmodifiedMap);
This function will throw NullPointerException if the keyMapper, valueMapper, or mergeFunction is null.

10. Collectors.summingInt() Example


summingInt() Method does the summation of all extracted elements from the stream and returns Integer.

Syntax:
public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)

Example:

Finding the sum of all emp id's using summingInt() method.
int sumOfEmpIds = employeeList.stream().collect(Collectors.summingInt((Employee e) -> e.getId()));
System.out.println("Collectors.summingInt : " + sumOfEmpIds);

Output:
Collectors.summingInt : 1000
This method is to sum of int values. 

Similar to this, Collector api has methods for summingLong(), summingDouble().
public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)

summingDouble() Example:

Now, let us see the code to get the sum of salaries of all employees.
double sumOfEmpSalss = employeeList.stream().collect(Collectors.summingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.summingDouble : " + sumOfEmpSalss);
Output:

Collectors.summingDouble : 1400000.0

11. Collectors.averagingInt() / averagingLong() / averagingDouble() Examples


Collectors api has methods to get the average for integer, Long and Double values. These methods become now very handy to perform avg operations.
Internally arithmetic logic is applied for stream elements.

Syntax:
public static <T> Collector<T,?,Double> averagingInt(ToIntFunction<? super T> mapper)
public static <T> Collector<T,?,Double> averagingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,Double> averagingDouble(ToDoubleFunction<? super T> mapper)
Here all these versions of methods return Double rather that its names describe.

Examples:
double avgOfEmpSalss = employeeList.stream().collect(Collectors.averagingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.averagingDouble avg sal: " + avgOfEmpSalss);

Output:
Collectors.averagingDouble avg sal: 350000.0

12. Collectors.counting() Example


Syntax:
public static <T> Collector<T,?,Long> counting()

This method returns a Long value and just counts the values present in the Stream.
long count = employeeList.stream().collect(Collectors.counting());
System.out.println("Collectors.counting() : Count : " + count);

Output:
Collectors.counting() : Count : 4

13. Collectors.joining() Example


Syntax:
public static Collector<CharSequence,?,String> joining()
public static Collector<CharSequence,?,String> joining(CharSequence delimiter)
public static Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
 
Joining() method does append the all contents of Stream the order they appear.

This is mainly designed for Strings with Stream<String>.

All versions of joining() methods return String.

joining() Example:

String joinedStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining());
System.out.println("joinedStr by using joining() method : "+joinedStr);

 
Output:
joinedStr by using joining() method : SundarPichaiLarryPage
 
This joining() method just concatenates without adding any delimiter.

joining(CharSequence delimiter) Example:

If we want to add some delimiter then we should use this variant of the method.
String joinedDelimiterStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining(" * "));
System.out.println("joinedDelimiterStr by using joining(Delimiter) method : "+joinedDelimiterStr);

 
Output:
joinedDelimiterStr by using joining(Delimiter) method : Sundar * Pichai * Larry * Page
 
Now observe the output that added * delimiter for each name.

joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) Example:

Example code to add some value to before and after delimiter is applied.

prefix will be added at the beginning after concatenating the delimiter to all values.
suffix will be added at the end after concatenating the delimiter to all values.
String joinePrePostStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining("*", "@", "|"));
System.out.println("joinePrePostStr by using joining(Delimiter) method : "+joinePrePostStr);

 
Output:
joinePrePostStr by using joining(Delimiter) method : @Sundar*Pichai*Larry*Page| 

14. Collectors.groupingBy() Example


This groupingBy function works similar to the Oracle GROUP BY clause.

GroupingBy collector is used for grouping objects by some property and storing results in a Map instance.

We will see the example of the group by an employee region.
Map<String, List<Employee>> groupByRegion = employeeList.stream().collect(Collectors.groupingBy((Employee e) -> e.getRegion()));
System.out.println("groupByRegion :: "+groupByRegion);

Output:
groupByRegion :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}

The below two employees are stored under the "North America" region. 

Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]

By default, Map values are stored in the List.

If we want to make value is set then we should specify to the groupingBy() method as below.
Map<String, Set<Employee>> groupByRegionSet = employeeList.stream().collect(Collectors.groupingBy((Employee e) -> e.getRegion(), Collectors.toSet()));

15. Collectors.partitioningBy() Example


Syntax:
public static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)

First, Take a look at the syntax.

From syntax, This method returns a Map and Boolean is key, List<T> as value. The returned Map always contains mappings for both false and true keys only.
Map<Boolean, List<Employee>> partitionByAge = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30));
System.out.println("partitionByAge :: "+partitionByAge);

partitioningBy() method takes Predicate Functional Interface which returns Boolean. Because of this, Key is always determined as Boolean and value is Employee object.

Output:
partitionByAge :: {false=[Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], true=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]}

Returned Map value is always a List. If you want to change it to another collection then we must use the below variant of groupingBy() method.

public static <T,D,A> Collector<T,?,Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector< super T,A,D> downstream)

Examples:
// Set as Map value
Map<Boolean, Set<Employee>> partitionByAgeSet = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toSet()));

// LinkedList as Map value
Map<Boolean, LinkedList<Employee>> partitionByAgeLinedList = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toCollection(LinkedList::new)));

// TreeSet as Map value
Map<Boolean, TreeSet<Employee>> partitionByAgeTreeSet = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toCollection(TreeSet::new)));
To make working for TreeSort, the Employee class must implement Comparable interface. Otherwise, will throw ClassCastException.

Note: If a partition has no elements, its value in the resulting Map will be an empty List.

16. Collectors.toConcurrentMap() Example


We have seen already toMap() in this post. If we want to store the result in Concurrent Map then we should use the toConcurrentMap() method.

Syntax:
public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)
public static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapFactory)

Examples:
Map<Integer, Employee> empConcurrentMap = employeeList.stream().collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity()));

17. Collectors.filtering() Example


Stream api already has a filter() function. But, this is a very convenient way to do at a single place doing condition and collecting into List or Set.

Based on developer choice, We can choose Collectors or Stream api. Mostly, All use stream api filter() method.

Syntax:
public static <T,A,R> Collector<T,?,R> filtering(Predicate<? super T> predicate, Collector<? super T,A,R> downstream)

Examples:

For all stream elements, the first Predicate will be executed first and next downstream will be applied.
List<Employee> filteredEmpList = employeeList.stream().collect(Collectors.filtering((Employee e) -> e.getAge() > 30, Collectors.toList()));
System.out.println("Collectors.filtering() - filteredEmpList : " + filteredEmpList);

Output:
Collectors.filtering() - filteredEmpList : [Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]

18. Collectors.flatMapping() Example


This is very useful to convert a Collection of Collections into a flat map. The flatMapping() collectors are most useful when used in a multi-level reduction, such as downstream of a groupingBy or partitioningBy. 

Syntax:
public static <T,U,A,R> Collector<T,?,R> flatMapping(Function<? super T,? extends Stream<? extends U>> mapper, Collector<? super U,A,R> downstream)
Example:

Creating LineItem class.
class LineItem {

private int itemId;
private String itemName;
private Date manfacturedDate;

public LineItem(int itemId, String itemName, Date manfacturedDate) {
super();
this.itemId = itemId;
this.itemName = itemName;
this.manfacturedDate = manfacturedDate;
}

public int getItemId() {
return itemId;
}

public void setItemId(int itemId) {
this.itemId = itemId;
}

public String getItemName() {
return itemName;
}

public void setItemName(String itemName) {
this.itemName = itemName;
}

public Date getManfacturedDate() {
return manfacturedDate;
}

public void setManfacturedDate(Date manfacturedDate) {
this.manfacturedDate = manfacturedDate;
}

@Override
public String toString() {
return "LineItem [itemId=" + itemId + ", itemName=" + itemName + ", manfacturedDate=" + manfacturedDate + "]";
}

}

Creating Customer class which has HAS-A relationship to LineIteam with List.
class Customer {
private int id;
private String name;
private boolean active;
private String gender;
private List<LineItem> lineItems;

public Customer(int id, String name, boolean active, String gender, List<LineItem> lineItems) {
super();
this.id = id;
this.name = name;
this.active = active;
this.gender = gender;
this.lineItems = lineItems;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean isActive() {
return active;
}

public void setActive(boolean active) {
this.active = active;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public List<LineItem> getLineItems() {
return lineItems;
}

public void setLineItems(List<LineItem> lineItems) {
this.lineItems = lineItems;
}

}
Creating Customer object 1
LineItem lineItem1 = new LineItem(1001, "Item 1", new Date(2010, 07, 01));
LineItem lineItem2 = new LineItem(1002, "Item 2", new Date(2020, 07, 01));
LineItem lineItem3 = new LineItem(1003, "Item 3", new Date(2030, 07, 01));
LineItem lineItem4 = new LineItem(1004, "Item 4", new Date(2040, 07, 01));
LineItem lineItem5 = new LineItem(1005, "Item 5", new Date(2050, 07, 01));

List<LineItem> lineItemsList1 = new ArrayList<>();
lineItemsList1.add(lineItem1);
lineItemsList1.add(lineItem2);
lineItemsList1.add(lineItem3);
lineItemsList1.add(lineItem4);
lineItemsList1.add(lineItem5);

Customer customer1 = new Customer(100, "Customer 1", true, "M", lineItemsList1);


Creating Customer object 2
LineItem lineItem6 = new LineItem(2001, "Item 6", new Date(2010, 07, 01));
LineItem lineItem7 = new LineItem(2002, "Item 7", new Date(2020, 07, 01));

List<LineItem> lineItemsList2 = new ArrayList<>();
lineItemsList2.add(lineItem6);
lineItemsList2.add(lineItem7);

Customer customer2 = new Customer(200, "Customer 2", true, "F", lineItemsList2);

Creating Customer object 3
LineItem lineItem8 = new LineItem(2003, "Item 8", new Date(2040, 07, 01));
LineItem lineItem9 = new LineItem(3004, "Item 9", new Date(2070, 07, 01));
LineItem lineItem10 = new LineItem(3005, "Item 10", new Date(2200, 07, 01));

List<LineItem> lineItemsList3 = new ArrayList<>();
lineItemsList3.add(lineItem8);
lineItemsList3.add(lineItem9);
lineItemsList3.add(lineItem10);

Customer customer3 = new Customer(300, "Customer 3", true, "M", lineItemsList3);

Creating Customer object 4
Customer customer4 = new Customer(400, "Customer 4", true, "F", new ArrayList<LineItem>());

Adding all 4 countomers to List.

List<Customer> customersList = new ArrayList<>();
customersList.add(customer1);
customersList.add(customer2);
customersList.add(customer3);
customersList.add(customer4);

Using flatMapping() method to find the LineItems by gender.
Map<String, Set<LineItem>> itemsByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.toSet())));

System.out.println("itemsByGender : " + itemsByGender);	

Output:
itemsByGender : {F=[LineItem [itemId=2001, itemName=Item 6, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=2002, itemName=Item 7, manfacturedDate=Sun Aug 01 00:00:00 IST 3920]],
 M=[LineItem [itemId=1001, itemName=Item 1, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=1005, itemName=Item 5, manfacturedDate=Tue Aug 01 00:00:00 IST 3950], LineItem [itemId=1004, itemName=Item 4, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=1002, itemName=Item 2, manfacturedDate=Sun Aug 01 00:00:00 IST 3920], LineItem [itemId=1003, itemName=Item 3, manfacturedDate=Fri Aug 01 00:00:00 IST 3930], LineItem [itemId=2003, itemName=Item 8, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=3004, itemName=Item 9, manfacturedDate=Sat Aug 01 00:00:00 IST 3970], LineItem [itemId=3005, itemName=Item 10, manfacturedDate=Sun Aug 01 00:00:00 IST 4100]]}
Finding the count LineItemas count by gender.

Now instead of calling collecting the output of flatMapping() into Set, invoke Collectors.counting() that count the number of Line items by Gender.
Map<String, Long> itemsCountByGender = customersList.stream().collect(Collectors.groupingBy((Customer c) -> c.getGender(),Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.counting())));

Output:
itemsCountByGender {F=2, M=8}

19. Collectors.maxBy() Example


maxBy() method finds the max element from the stream. To find the max element, we must pass the Comparator implementation to this method as argument.
This method does as same as reducing(BinaryOperator.maxBy(comparator)).

Syntax:
public static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator)

This method returns the Optional object. This object avoids NullPointerException by using isPresent() method of Optional class.

Example:

Collectors.maxBy() Example to find highest emp id object.
Comparator<Employee> empComparator = (e1, e2) -> e1.getId() - e2.getId();
Optional<Employee> empMaxOptional = employeeList.stream().collect(Collectors.maxBy(empComparator));
if(empMaxOptional.isPresent()) {
System.out.println("Max Emp : "+empMaxOptional.get());
}
Output:
Max Emp : Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]

20. Collectors.minBy() Example


minBy() is opposite to the maxBy() method. minBy() method is used to find a minimum element from the Stream. We should pass the Comparator as an argument.

This method does as same as reducing(BinaryOperator.minBy(comparator)).

Syntax:
public static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator)
This method also returns an Optional instance similar to the maxBy() method.

Example:

Collectors.minBy() Example to find min emp id object.
Optional<Employee> empminOptional = employeeList.stream().collect(Collectors.minBy(empComparator));
if (empminOptional.isPresent()) {
System.out.println("Min Emp : " + empminOptional.get());
}

Output:
Min Emp : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]

21. Collectors.reducing() Example


Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator. The result is described as an Optional<T>.

Syntax:
public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)
public static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> op)
public static <T,U> Collector<T,?,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

Example:
// Collectors.reducing() Example
Optional<Employee> reducingOptinal = employeeList.stream().collect(Collectors.reducing(BinaryOperator.minBy(empComparator)));
if (reducingOptinal.isPresent()) {
System.out.println("Min Emp using reducing() method : " + reducingOptinal.get());
}
Output:

Min Emp using reducing() method : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]

We can find the max emp element using maxBy static method of BinaryOperator.

22. Collectors.summarizingDouble() / summarizingInt() / summarizingLong() Example


summarizingDouble() method does statistics operations on double values in the stream.

This method returns DoubleSummaryStatistics which holds count, min, max, sum, and the average for all double values in the stream. This helps like a utility method.

Syntax:
public static <T> Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)

Example:
DoubleSummaryStatistics doubleSummaryStatistics = employeeList.stream().collect(Collectors.summarizingDouble((Employee e) -> e.getSal()));
System.out.println("Statistics summary on employees salary : "+doubleSummaryStatistics);

Output:
DoubleSummaryStatistics{count=4, sum=1400000.000000, min=50000.000000, average=350000.000000, max=450000.000000}
In the same way for Integer and Long values separate methods provided to get the statistics using summarizingInt() and summarizingLong().
public static <T> Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)

23. Collectors.teeing() Example


teeing() method is used to combine two Collectors output using a special merger function. This method is added in Java 12.

Syntax:
public static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)

The above syntax is difficult to understand and simplified as below.
public static Collector teeing(Collector collector1, Collector collector2, BiFunction merger)
Simple words, This method accepts two collectors and one merger function. Merger function takes outputs of two collectors and performs operations. Finally, returns some value or object or maybe collection.

Example:

Collectors teeing() example to find the average of first 100 numbers.
// Converting 1 to 100 numbers into Stream integer.
List<Integer> intList = new ArrayList<>();
IntStream.range(1, 100).forEach(i -> intList.add(i));

// Calling teeing method.
Double average = intList.stream().collect(
Collectors.teeing(Collectors.summingDouble(i -> i), Collectors.counting(), (sum, n) -> sum / n));

System.out.println("Average of first 100 numbers: " + average);

Collector 1: Finds the sum of 100 numbers from the stream.
Collectors.summingDouble(i -> i) 
Collector 2: Finds count of numbers in the stream.
Collectors.counting()
Merger: Takes sum and count as input and does the average operation.
(sum, n) -> sum / n)

Output:
Average of first 100 numbers: 50.0

24. Complete Collectors Methods Examples

All Collectors utility class all methods with examples in a single program.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.DoubleSummaryStatistics;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Java 8 Collectors api Examples
 * 
 * @author Venkatesh
 *
 */
public class Java8CollectorExamples {

public static void main(String[] args) {

// Creating a Employee List - Example

List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(100, "Sundar", 47, "North America", 450000));
employeeList.add(new Employee(200, "Pichai", 25, "North America", 50000));
employeeList.add(new Employee(300, "Larry", 30, "Asia", 450000));
employeeList.add(new Employee(400, "Page", 59, "Africa", 450000));

// Collectors.toList() Example

List<String> namesList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println(namesList);

// Collectors.toSet() Example
Set<String> regionSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toSet());
System.out.println(regionSet);
regionSet.add("hello");
System.out.println(regionSet);

// Collectors.toUnmodifiableSet() Example

Set<Double> unmodifiableSet = employeeList.stream().map(e -> e.getSal())
.collect(Collectors.toUnmodifiableSet());
System.out.println(unmodifiableSet);
// unmodifiableSet.add(10983d);

// employeeList.add(null);

Set<Employee> empSet = employeeList.stream().collect(Collectors.toUnmodifiableSet());

// Collectors.toUnmodifiableList(() Example
// employeeList.add(null);
List<Double> unmodifiableList = employeeList.stream().map(e -> e.getSal())
.collect(Collectors.toUnmodifiableList());
System.out.println(unmodifiableList);

// Collectors.toCollection() Example

List<String> namesLinkedList = employeeList.stream().map(e -> e.getName())
.collect(Collectors.toCollection(LinkedList::new));
System.out.println(namesLinkedList);

Set<String> regionTreeSet = employeeList.stream().map(e -> e.getRegion())
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(regionTreeSet);

// Collectors.toMap() Example
Map<Integer, Employee> empMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity()));
System.out.println(empMap);

// with duplicate key. uncomment to work with toMap() for duplicate merger.
// employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Map<Integer, Employee> empDupMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);

// Collectors.toUnmodifiableMap() Example
Map<Integer, Employee> empUnmodifiedMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empUnmodifiedMap);

// Collector.summingInt() Example

int sumOfEmpIds = employeeList.stream().collect(Collectors.summingInt((Employee e) -> e.getId()));
System.out.println("Collectors.summingInt : " + sumOfEmpIds);

// Collector.summingInt() Example

double sumOfEmpSalss = employeeList.stream().collect(Collectors.summingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.summingDouble : " + sumOfEmpSalss);

// Collectors.averagingInt() / averagingLong() / averagingDouble() Examples

double avgOfEmpSalss = employeeList.stream().collect(Collectors.averagingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.averagingDouble avg sal: " + avgOfEmpSalss);

// Collectors.counting() Example

long count = employeeList.stream().collect(Collectors.counting());
System.out.println("Collectors.counting() : Count : " + count);

// Collectors.joining() Example
String joinedStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining());
System.out.println("joinedStr by using joining() method : " + joinedStr);

String joinedDelimiterStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining(" * "));
System.out.println("joinedDelimiterStr by using joining(Delimiter) method : " + joinedDelimiterStr);

String joinePrePostStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining("*", "@", "|"));
System.out.println("joinePrePostStr by using joining(Delimiter) method : " + joinePrePostStr);

// Collectors.groupingBy() Example
Map<String, List<Employee>> groupByRegion = employeeList.stream()
.collect(Collectors.groupingBy((Employee e) -> e.getRegion()));
System.out.println("groupByRegion :: " + groupByRegion);

// groupingBy for set.
Map<String, Set<Employee>> groupByRegionSet = employeeList.stream()
.collect(Collectors.groupingBy((Employee e) -> e.getRegion(), Collectors.toSet()));
System.out.println("groupByRegionSet :: " + groupByRegionSet);

// Collectors.partitioningBy() Example
Map<Boolean, List<Employee>> partitionByAge = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30));
System.out.println("partitionByAge :: " + partitionByAge);

// Set as Map value
Map<Boolean, Set<Employee>> partitionByAgeSet = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30, Collectors.toSet()));

// LinkedList as Map value
Map<Boolean, LinkedList<Employee>> partitionByAgeLinedList = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30, Collectors.toCollection(LinkedList::new)));

// TreeSet as Map value
/*
* Map<Boolean, TreeSet<Employee>> partitionByAgeTreeSet = employeeList.stream()
* .collect(Collectors.partitioningBy(e -> e.getAge() > 30,
* Collectors.toCollection(TreeSet::new)));
*/

// Collectors.toConcurrentMap() Example
Map<Integer, Employee> empConcurrentMap = employeeList.stream()
.collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity()));
System.out.println(empConcurrentMap);

// with duplicate key. uncomment to work with toMap() for duplicate merger.
// employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Map<Integer, Employee> empDupConcurrentMap = employeeList.stream()
.collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);

// Collectors.filtering() Example
List<Employee> filteredEmpList = employeeList.stream()
.collect(Collectors.filtering((Employee e) -> e.getAge() > 30, Collectors.toList()));
System.out.println("Collectors.filtering() - filteredEmpList : " + filteredEmpList);

// Collectors.flatMapping() Example

LineItem lineItem1 = new LineItem(1001, "Item 1", new Date(2010, 07, 01));
LineItem lineItem2 = new LineItem(1002, "Item 2", new Date(2020, 07, 01));
LineItem lineItem3 = new LineItem(1003, "Item 3", new Date(2030, 07, 01));
LineItem lineItem4 = new LineItem(1004, "Item 4", new Date(2040, 07, 01));
LineItem lineItem5 = new LineItem(1005, "Item 5", new Date(2050, 07, 01));

List<LineItem> lineItemsList1 = new ArrayList<>();
lineItemsList1.add(lineItem1);
lineItemsList1.add(lineItem2);
lineItemsList1.add(lineItem3);
lineItemsList1.add(lineItem4);
lineItemsList1.add(lineItem5);

Customer customer1 = new Customer(100, "Customer 1", true, "M", lineItemsList1);

LineItem lineItem6 = new LineItem(2001, "Item 6", new Date(2010, 07, 01));
LineItem lineItem7 = new LineItem(2002, "Item 7", new Date(2020, 07, 01));

List<LineItem> lineItemsList2 = new ArrayList<>();
lineItemsList2.add(lineItem6);
lineItemsList2.add(lineItem7);

Customer customer2 = new Customer(200, "Customer 2", true, "F", lineItemsList2);

LineItem lineItem8 = new LineItem(2003, "Item 8", new Date(2040, 07, 01));
LineItem lineItem9 = new LineItem(3004, "Item 9", new Date(2070, 07, 01));
LineItem lineItem10 = new LineItem(3005, "Item 10", new Date(2200, 07, 01));

List<LineItem> lineItemsList3 = new ArrayList<>();
lineItemsList3.add(lineItem8);
lineItemsList3.add(lineItem9);
lineItemsList3.add(lineItem10);

Customer customer3 = new Customer(300, "Customer 3", true, "M", lineItemsList3);
Customer customer4 = new Customer(400, "Customer 4", true, "F", new ArrayList<LineItem>());

List<Customer> customersList = new ArrayList<>();
customersList.add(customer1);
customersList.add(customer2);
customersList.add(customer3);
customersList.add(customer4);

Map<String, Set<LineItem>> itemsByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.toSet())));
System.out.println("itemsByGender : " + itemsByGender);

Map<String, Long> itemsCountByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.counting())));
System.out.println("itemsCountByGender " + itemsCountByGender);

// Collectors.maxBy() Example
Comparator<Employee> empComparator = (e1, e2) -> e1.getId() - e2.getId();
Optional<Employee> empMaxOptional = employeeList.stream().collect(Collectors.maxBy(empComparator));
if (empMaxOptional.isPresent()) {
System.out.println("Max Emp : " + empMaxOptional.get());
}

// Collectors.minBy() Example
Optional<Employee> empminOptional = employeeList.stream().collect(Collectors.minBy(empComparator));
if (empminOptional.isPresent()) {
System.out.println("Min Emp : " + empminOptional.get());
}

// Collectors.reducing() Example
Optional<Employee> reducingOptinal = employeeList.stream()
.collect(Collectors.reducing(BinaryOperator.minBy(empComparator)));
if (reducingOptinal.isPresent()) {
System.out.println("Min Emp using reducing() method : " + reducingOptinal.get());
}

// Collectors.summarizingDouble() Example
DoubleSummaryStatistics doubleSummaryStatistics = employeeList.stream()
.collect(Collectors.summarizingDouble((Employee e) -> e.getSal()));
System.out.println("Statistics summary on employees salary : " + doubleSummaryStatistics);

// Converting 1 to 100 numbers into Stream integer.
List<Integer> intList = new ArrayList<>();
IntStream.range(1, 100).forEach(i -> intList.add(i));

// Calling teeing method.
Double average = intList.stream().collect(
Collectors.teeing(Collectors.summingDouble(i -> i), Collectors.counting(), (sum, n) -> sum / n));

System.out.println("Average of first 100 numbers: " + average);

}
}
Output:
[Sundar, Pichai, Larry, Page]
[Asia, Africa, North America]
[Asia, Africa, hello, North America]
[50000.0, 450000.0]
[450000.0, 50000.0, 450000.0, 450000.0]
[Sundar, Pichai, Larry, Page]
[Africa, Asia, North America]
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
Collectors.summingInt : 1000
Collectors.summingDouble : 1400000.0
Collectors.averagingDouble avg sal: 350000.0
Collectors.counting() : Count : 4
joinedStr by using joining() method : SundarPichaiLarryPage
joinedDelimiterStr by using joining(Delimiter) method : Sundar * Pichai * Larry * Page
joinePrePostStr by using joining(Delimiter) method : @Sundar*Pichai*Larry*Page|
groupByRegion :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}
groupByRegionSet :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}
partitionByAge :: {false=[Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], true=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
Collectors.filtering() - filteredEmpList : [Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]
itemsByGender : {F=[LineItem [itemId=2001, itemName=Item 6, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=2002, itemName=Item 7, manfacturedDate=Sun Aug 01 00:00:00 IST 3920]], M=[LineItem [itemId=1001, itemName=Item 1, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=1005, itemName=Item 5, manfacturedDate=Tue Aug 01 00:00:00 IST 3950], LineItem [itemId=1004, itemName=Item 4, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=1002, itemName=Item 2, manfacturedDate=Sun Aug 01 00:00:00 IST 3920], LineItem [itemId=1003, itemName=Item 3, manfacturedDate=Fri Aug 01 00:00:00 IST 3930], LineItem [itemId=2003, itemName=Item 8, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=3004, itemName=Item 9, manfacturedDate=Sat Aug 01 00:00:00 IST 3970], LineItem [itemId=3005, itemName=Item 10, manfacturedDate=Sun Aug 01 00:00:00 IST 4100]]}
itemsCountByGender {F=2, M=8}
Max Emp : Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]
Min Emp : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]
Min Emp using reducing() method : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]
Statistics summary on employees salary : DoubleSummaryStatistics{count=4, sum=1400000.000000, min=50000.000000, average=350000.000000, max=450000.000000}
Average of first 100 numbers: 50.0



25. Conclusion


In this tutorial, We have covered in-depth on Collectors API.

First, Covered the introduction to the Collectors api and what it does.

Next, we've seen the example programs on each and every method of Collectors.

All methods are declared as Static in Collectors class. So, Directly methods can be accessed with class name E.g. Collectors.toList(), Collectors.groupingBy() etc.

Commonly used method of Collectors are toList(), toMap(), toCollection(), joining(), summingInt(), groupingBy() and partitioningBy().

All the examples shown are available over GitHub.




No comments:

Post a Comment

Please do not add any spam links in the comments section.