Java 8 Partitioning with Collectors | partitioningBy method tutorial with examples
Introduction
Java 8 Partitioning with Collectors tutorial explains how to use the predefined Collector returned by
Since, our objective is to solve this partitioning problem programmatically, hence we create a representation of this problem in Java. We define an element Color which represents the block, i.e. Color green represents green blocks and likewise Color red represents red blocks. We then create a Stream of these Color objects and use the
This is how the above scenario would look like when drawn as a diagram -
As shown in above diagram, application of
The Color objects are thus partitioned into 2 Lists which can be retrieved by invoking
Now that we have seen how partitioning with collectors works, its time to see how to implement partitioning in code using the predefined Collector instance returned by the static method
Where,
- input is
- output is a Collector with finisherClick to Read tutorial on 4 components of Collectors incl. 'finisher'(return type) as a
When the
Let us see a Java 8 code example showing the
OUTPUT of the above code
Explanation of the code
Now, have a look at the diagram below which extends the previous visual Colors example by calling the overloaded
In the above diagram, output from the
Having understood the working of the overloaded
Where,
- first parameter is
- second parameter is a
- output is a Collector with finisher(return type) as a
Let us extend the previous code example, where we partitioned the employees into 2 groups based on whether they were older than 30 years or not, and pass the
(Note - The
OUTPUT of the above code
Explanation of the code
Conclusion
In this tutorial we first understood what is meant by partitioning of Streams using a
partitioningBy()
method of java.util.stream.Collectors
class with examples. The tutorial starts off with explaining the concept of partitioning data in Streams with a visual example. It then discusses the advantage that partitioning Streams with Collectors provides over filtering. The partitioningBy() method is then discussed and its usage is shown with a code example. Next, we will take a look at the 2nd variant of partitioningBy()
method, by extending the visual example we saw earlier, to see how a Collector can be used as to again collect the data returned by the application of partitioningBy()
method. Lastly, we will see a Java code example showing the overloaded partitioningBy()
method with a second Collector in action.
(Note - This tutorial assumes that you are familiar with basics of Java 8 CollectorsRead Tutorial explaining basics of Java 8 Collectors.)
Understanding the concept of 'partitioning' using Collectors
Given a stream of objects, many-a-times we need to check whether object(s) in the given stream match a specific criteria or not. Instead of writing logic for iterating over the stream elements and checking each object whether it matches the criteria (which is more of an imperative rather than functionalClick to understand the difference between the two programming styles style of programming), Java 8 Collectors allow declarative partitioning of elements into 2 groups which satisfy/don't satisfy the given PredicateClick to read detailed tutorial on Predicate Functional Interfaces of type T
.
Example explaining the basic concept of partitioning
Suppose you have a collection of blocks. These blocks are in 2 colors - green and red. Now, you want to partition the blocks into their separate color-coded groups. I.e. one collection of green blocks and another collection of red blocks.Since, our objective is to solve this partitioning problem programmatically, hence we create a representation of this problem in Java. We define an element Color which represents the block, i.e. Color green represents green blocks and likewise Color red represents red blocks. We then create a Stream of these Color objects and use the
Collectors.partitioningBy()
method to partition these objects into 2 lists - one for each color.Collectors.partitioningBy()
method to the Stream of Color objects, with predicate condition as ‘Color.isRed()
’, results in 2 separate lists of objects being created. These lists are in 2 separate entries in a Map. The objects which return true for ‘Color.isRed()
’, i.e. Red Color objects, are stored in the Map entry with key ‘true
’. Similarly, the remaining green objects which return false for ‘Color.isRed()
’ condition are store in the Map entry with key ‘false
’.The Color objects are thus partitioned into 2 Lists which can be retrieved by invoking
Map.get(true)
and Map.get(false)
respectively.
Advantage of partitioning using Collectors versus the Stream.filter() operation
If you are aware of the Stream.filter()Click to Read tutorial on filtering with Streams operation then you would have realized by now that the same conditional fetching of objects based on a provided Predicate
can be accomplished by filtering stream elements as well. However, the partitioning operation provides a simple but helpful advantage over filtering. At the end of the partitioning operation, the method returns back both the groups of elements - one that satisfy the given Predicate
and the ones that don't- together. Filtering a stream can provide you the same two groups but you will need to invoke the filtering operation twice - one with the given Predicate and the second time with the negation of that Predicate.Now that we have seen how partitioning with collectors works, its time to see how to implement partitioning in code using the predefined Collector instance returned by the static method
Collectors.partitioningBy()
.
Two overloaded variants of Collectors.partitioningBy() method
At this point it is important to note that there are actually 2 overloaded static methods named partitioningBy()
in the Collectors
class. What we are looking at now is the first of these methods which accepts a Predicate
instance as its only parameter. There is a second overloaded Collectors.partitioningBy()
method as well which along with a Predicate
instance takes another Collector
instance as the second input parameter. We will look at the second partitioningBy()
method also in detail after we cover the first one.
Collectors.partitioningBy()
method
Collectors.partitioningBy()
method is defined with the following signature -
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)
- input is
predicate
which is an instance of a PredicateClick to read detailed tutorial on Predicate Functional Interfaces Functional Interface of type T
- output is a Collector with finisherClick to Read tutorial on 4 components of Collectors incl. 'finisher'(return type) as a
Map
with entries having ‘key,value’ pairs as ‘Boolean, List<T>
’When the
Stream.collect()
operation is invoked on a Stream
containing elements of type T
, with Collector<T>
returned by Collectors.partitioningBy(Predicate<T>)
method passed as parameter, what you get as the resultant collection from this terminal operationClick to Read Tutorial explaining intermediate & terminal Stream operations is a Map
containing the elements of the Stream
divided into two entries(or 'key,value' pairs). While the 1st map entry has key true
and value containing List<T>
of elements that satisfy the Predicate
condition, the 2nd map entry has key false
and value containing List<T>
of elements which do not satisfy the Predicate
condition.Let us see a Java 8 code example showing the
Collector
returned by Collectors.partitioningBy()
method in action.Java 8 code showing Collectors.partitioningBy() usage
package com.javabrahman.java8.collector;
import com.javabrahman.java8.Employee;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PartitioningWithCollectors {
static List<Employee> employeeList = Arrays.asList(new Employee("Tom Jones", 45),
new Employee("Harry Major", 26),
new Employee("Ethan Hardy", 65),
new Employee("Nancy Smith", 22),
new Employee("Catherine Jones", 21),
new Employee("James Elliot", 58),
new Employee("Frank Anthony", 55),
new Employee("Michael Reeves", 40));
public static void main(String args[]){
Map<Boolean,List<Employee>> employeeMap
= employeeList
.stream()
.collect(Collectors.partitioningBy((Employee emp) -> emp.getAge() > 30));
System.out.println("Employees partitioned based on Predicate - 'age > 30'");
employeeMap.forEach((Boolean key, List<Employee> empList) -> System.out.println(key +"->" + empList));
}
}
//Employee.java(POJO Class)
package com.javabrahman.java8;
public class Employee {
private String name;
private Integer age;
public Employee(String name, Integer age) {
this.name = name;
this.age = age;
}
//Getters and Setters of name & age go here
public String toString(){
return "Employee Name:"+this.name
+" Age:"+this.age;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Employee)) {
return false;
}
Employee empObj = (Employee) obj;
return this.age == empObj.age
&& this.name.equalsIgnoreCase(empObj.name);
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 17 + this.name.hashCode();
hash = hash * 31 + this.age;
return hash;
}
}
Employees partitioned based on Predicate - 'age > 30' false->[Employee Name:Harry Major Age:26, Employee Name:Nancy Smith Age:22, Employee Name:Catherine Jones Age:21] true->[Employee Name:Tom Jones Age:45, Employee Name:Ethan Hardy Age:65, Employee Name:James Elliot Age:58, Employee Name:Frank Anthony Age:55, Employee Name:Michael Reeves Age:40]
Employee
is the POJO class in the above example of which we create a Stream. It has two main attributes -name
andage
.employeeList
is a static list of 8Employee
s.- In the
main()
method ofPartitioningWithCollectors
class we create aStream
ofEmployee
s using thestream()
method ofList
interface. - On the stream of Employees we call the
collect()
method with thePredicate
instance being specified as its equivalent lambda expressionClick to read tutorial on Java 8 Lambda Expressions -(Employee emp) -> emp.getAge()>30)
. This predicate condition states that the employee's age should be greater than 30 years. - Lastly, the
Map
of employees partitioned by the predicate condition are printed usingMap.forEach()
method. The output is as expected - employees withage>30
are printed in a list corresponding to key valuetrue
, while those withage<=30
are printed as a list against key valuefalse
.
Overloaded Collectors.partitioningBy() with Collector as second parameter
To understand the utility and usage of the overloaded partitioningBy()
method, let us revisit the earlier example where we partitioned the collection of Color objects into red and green lists. However, what if your requirement was not the partitioned lists but instead you needed a count of red and green color objects as the final result of partitioning. Using a 2nd Collector
in this case, specifically the one returned by Collectors.counting()
method, is exactly what you need to get the count of each of these lists.Now, have a look at the diagram below which extends the previous visual Colors example by calling the overloaded
partitioningBy()
method with the counting collector - partitioningBy()
method is a map with values containing the count of red and green colors. In fact, the lists of colors were created by the partitioningBy()
method in this case as well, but then the counting collector was applied on the lists and a Map
was returned which had just the count of colors as the value for keys true
and false
.Having understood the working of the overloaded
partitioningBy()
method, let us now take a look at its formal definition -
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream)
- first parameter is
predicate
which is an instance of a Predicate Functional Interface- second parameter is a
Collector
- output is a Collector with finisher(return type) as a
Map
with entries having ‘key,value’ pairs as ‘Boolean, D>
’ where D
is the return type of the finisher function of second collector parameterLet us extend the previous code example, where we partitioned the employees into 2 groups based on whether they were older than 30 years or not, and pass the
Collector
returned by Collectors.counting()
method as the overloaded partitionBy()
method’s second parameter.(Note - The
Employee
class and employeeList
objects with their values remain the same as the previous code usage example and hence are not shown below for brevity.)Java 8 code showing Collectors.partitioningBy() method usage
Map<Boolean,Long> employeeMapCount =
employeeList.stream()
.collect(Collectors.partitioningBy(
(Employee emp) -> (emp.getAge() > 30),
Collectors.counting()
));
System.out.println("Employee count in the 2 partitioned age groups");
employeeMapCount.forEach((Boolean key,Long count) -> System.out.println(key +" count -> "+ count));
Employee count in the 2 partitioned age groups false count -> 3 true count -> 5
Collectors.partitioningBy()
is invoked withPredicate
lambda being same as earlier i.e.(Employee emp) -> emp.getAge()>30)
.- The second parameter to the
partitioningBy()
method is theCollector
returned byCollectors.counting()
method. - As expected, a
Map
of values is returned, namedemployeeMapCount
,which when printed usingMap.forEach()
method gives the count of employees in the 2 partitioned groups as3
and5
respectively fortrue
andfalse
keys.
Collector
along with its advantage over filtering using Stream.filter()
method. We then understood the working of predefined Collector
returned by Collectors.partitioningBy()
method with a visual example followed by a Java 8 code example. We then understood the working of the overloaded partitioning method with a second collector by extending the previous visual and code examples.Java 8 Collectors' Tutorials on JavaBrahman
Understanding Basics of Java 8 CollectorsClick to Read Tutorial explaining basics of Java 8 CollectorsCollectors.groupingBy()Click to Read Tutorial on Grouping with CollectorsCollectors.partitioningBy()Click to Read Partitioning using Collectors TutorialCollectors.counting()Click to Read Counting with Collectors TutorialCollectors.maxBy()/minBy()Click to Read Tutorial on finding max/min with CollectorsCollectors.joining()Click to Read Tutorial on joining as a String using CollectorsCollectors.collectingAndThen()Click to Read Tutorial on collectingAndThen CollectorCollectors.averagingInt() /averagingLong() /averagingDouble()Click to Read Tutorial on Averaging CollectorCollectors.toCollection()Click to Read Tutorial on Collectors.toCollection CollectorCollectors.mapping()Click to Read Tutorial on Mapping Collector
Understanding Basics of Java 8 CollectorsClick to Read Tutorial explaining basics of Java 8 CollectorsCollectors.groupingBy()Click to Read Tutorial on Grouping with CollectorsCollectors.partitioningBy()Click to Read Partitioning using Collectors TutorialCollectors.counting()Click to Read Counting with Collectors TutorialCollectors.maxBy()/minBy()Click to Read Tutorial on finding max/min with CollectorsCollectors.joining()Click to Read Tutorial on joining as a String using CollectorsCollectors.collectingAndThen()Click to Read Tutorial on collectingAndThen CollectorCollectors.averagingInt() /averagingLong() /averagingDouble()Click to Read Tutorial on Averaging CollectorCollectors.toCollection()Click to Read Tutorial on Collectors.toCollection CollectorCollectors.mapping()Click to Read Tutorial on Mapping Collector