Partitioning in Java 8

  • Last Updated: June 10, 2024
  • By: javahandson
  • Series
img

Partitioning in Java 8

In this article, we will understand what is Partitioning in Java 8. We will also learn about different variants of partitioning methods with examples.

 

What is partitioning

Partitioning is a special case of grouping. Here we will use a predicate function to partition the elements of the Stream into 2 groups and store them in a map.

As we are using a predicate function for partitioning hence the resultant key of the map will be a boolean value therefore there can be at most two different groups one for true and one for false.

The Collectors class has given two overloaded partitioningBy methods that help to group the elements based on a predicate. If you are not aware of what a predicate is then you can refer to this article on predefined interfaces.

Variants of partitioningBy method

There are 2 variants of partitioningBy method present in the Collectors class.

  1. Using only predicate
  2. Using a predicate and a downstream collector

1. Using only predicate

Syntax:

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

Method Signature Explained:

a. static<T>: This indicates that partitioningBy is a static method. <T> is a generic type parameter where T is the type of the input elements.

b. Collector<T, ?, Map <Boolean, List<T>>>: The method returns a Collector that when applied to a stream of type T, will produce a Map where:

  • The keys are of type Boolean.
  • The values are lists of type T.

? indicates that the type of the intermediate result is unspecified and will be handled by the implementation

c. predicate: A Predicate that returns true or false for each element. This predicate determines how the elements will be partitioned.

Write a program to filter the even and odd numbers.

package com.javahands.collectors.partitioning;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PartitioningEx {
    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10);
        Map<Boolean, List<Integer>> map = list.stream().collect(Collectors
                .partitioningBy(number -> number % 2 == 0));
        System.out.println(map);
    }
}
Output: {false=[1, 3, 5, 7, 7, 9], true=[2, 2, 4, 6, 8, 10]}

In the above example, the predicate function is to filter the even numbers hence the resultant key for the even numbers is true whereas the remaining are odd numbers so the resultant key for the odd numbers is false.

If we write a predicate function to filter the odd numbers then the resultant key for the odd numbers will be true and the key for the even numbers will be false. Let’s check it below.

package com.javahands.collectors.partitioning;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PartitioningEx {
    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10);
        Map<Boolean, List<Integer>> map = list.stream().collect(Collectors
                .partitioningBy(number -> number % 2 != 0));
        System.out.println(map);
    }
}
Output: {false=[2, 2, 4, 6, 8, 10], true=[1, 3, 5, 7, 7, 9]}

2. Using a predicate and a downstream collector

Syntax:

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

Method Signature Explained:

a. static<T, D, A>: This indicates that partitioningBy is a static method. <T, D, A> are generic type parameters where:

  • T is the type of input elements
  • D is the type of the result of the downstream collector
  • A is the type of the intermediate accumulation type of the downstream collector

b. Collector<T, ?, Map>: The method returns a Collector. When this method is applied to a stream of type T it will produce a Map where:

  • The keys are of type Boolean.
  • The values are lists of type T.

? indicates that the type of the intermediate result is unspecified and will be handled at the time of implementation

c. predicate: A Predicate that returns true or false for each element. This predicate determines how the elements will be partitioned.

d. Collector<? super T, A, D> downstream: This downstream collector specifies how the partitioned elements will be collected further.

  • T represents the type of the elements in the stream that is being processed. ? super T means that the collector can accept elements of type T or any of its superclasses.
  • A is the type of intermediate container that holds the elements when the execution is going on.
  • D is the final result type of the collection process after all elements have been accumulated.

Write a program to filter the even and odd numbers and store the numbers in a Set (instead of a list)

Usually, if we are not giving any downstream collector then the numbers are getting stored in a list (Duplicate elements will be stored).

In the below program, we will explicitly specify the downstream collector so as to store the numbers in a set, so that duplicate elements will be ignored.

package com.javahands.collectors.partitioning;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class PartitioningEx {
    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10);
        Map<Boolean, Set<Integer>> map = list.stream().collect(Collectors
                .partitioningBy(number -> number % 2 == 0, Collectors.toSet()));
        System.out.println(map);
    }
}
Output: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}

Write a program to count the number of even and odd numbers in a list.

package com.javahands.collectors.partitioning;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.partitioningBy;

public class PartitioningEx {
    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 10);
        Predicate<Integer> predicate = number -> number % 2 == 0;
        Map<Boolean, Long> map = list.stream().collect(partitioningBy(predicate, counting()));

        System.out.println("Count of even numbers : " + map.get(true));
        System.out.println("Count of odd numbers : " + map.get(false));
    }
}
Output: 
Count of even numbers : 5
Count of odd numbers : 4

SummaryStatistics as a downstream collector

We will go a step further and check how we can use SummaryStatistics as a downstream collector instead of just using a primitive type a List or a Set. This will give you a better understanding of partitioningBy method and how to use different downstream collectors.

To know more about summary statistics in Java 8 you can refer to this article summarizing methods

Write a program to partition the students in 2 groups based on whether their marks are greater than 400 or not. For each group, we want to gather statistics about their marks, such as the average marks, the maximum marks, and the count of students in each group.

package com.javahands.collectors.partitioning;

public class Student {

    int rollNumber;
    String name;
    int marks;

    public Student(int rollNumber, String name, int marks) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.marks = marks;
    }

    public int getMarks() {
        return marks;
    }
}
package com.javahands.collectors.partitioning;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PartitioningDemo {
    public static void main(String[] args) {

        List<Student> students = Arrays.asList(
                new Student(101, "Suraj", 450),
                new Student(102, "Iqbal", 470),
                new Student(103, "Amar", 330),
                new Student(104, "Amit", 400),
                new Student(105, "Suchit", 380),
                new Student(106, "Kartik", 290));

        // Define a predicate to partition employees based on a salary threshold
        Predicate<Student> marksAbove400 = student -> student.getMarks() > 400;

        // Partition students based on the predicate and collect marks statistics for each group
        Map<Boolean, IntSummaryStatistics> salaryStatistics = students.stream()
                .collect(Collectors.partitioningBy(
                        marksAbove400,
                        Collectors.summarizingInt(Student::getMarks)
                ));

        // Print the results
        System.out.println("Marks statistics for students scored more than 400:");
        System.out.println(salaryStatistics.get(true));

        System.out.println("Marks statistics for students scored less than or equal to 400:");
        System.out.println(salaryStatistics.get(false));
    }
}
Output:
Marks statistics for students scored more than 400:
IntSummaryStatistics{count=2, sum=920, min=450, average=460.000000, max=470}

Marks statistics for students scored less than or equal to 400:
IntSummaryStatistics{count=4, sum=1400, min=290, average=350.000000, max=400}

So this is all about partitioning in Java 8. If you have any questions on this topic, please raise them in the comments section. If you liked this article then please share this post with your friends and colleagues.

Leave a Comment