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 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.

This is how the above scenario would look like when drawn as a diagram –

Java 8 Collectors.partitioningBy() with Predicate
As shown in above diagram, application of 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)

Where,
     – 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;
  }
}
 OUTPUT of the above code
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]

Explanation of the code

  • Employee is the POJO class in the above example of which we create a Stream. It has two main attributes – name and age.
  • employeeList is a static list of 8 Employees.
  • In the main() method of PartitioningWithCollectors class we create a Stream of Employees using the stream() method of List interface.
  • On the stream of Employees we call the collect() method with the Predicate 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 using Map.forEach() method. The output is as expected – employees with age>30 are printed in a list corresponding to key value true, while those with age<=30 are printed as a list against key value false.

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 -

Java 8 Collectors.partitioningBy() with Predicate
In the above diagram, output from the 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)

Where,
     - 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 aMap with entries having ‘key,value’ pairs as ‘Boolean, D>’ where D is the return type of the finisher function of second collector parameter

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 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));
 OUTPUT of the above code
Employee count in the 2 partitioned age groups
false count -> 3
true count -> 5
Explanation of the code

  • Collectors.partitioningBy() is invoked with Predicate lambda being same as earlier i.e. (Employee emp) -> emp.getAge()>30).
  • The second parameter to the partitioningBy() method is the Collector returned by Collectors.counting() method.
  • As expected, a Map of values is returned, named employeeMapCount,which when printed using Map.forEach() method gives the count of employees in the 2 partitioned groups as 3 and 5 respectively for true and false keys.

Conclusion - In this tutorial we first understood what is meant by partitioning of Streams using a 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.

 

Digiprove sealCopyright © 2014-2017 JavaBrahman.com, all rights reserved.