Java Collectors recipes

Java Collectors is a utility class with several reduction operations. Here a few of the example you can do with the Collectors class. For example, grouping functionality first.

import java.util.stream.Collectors;
import java.util.stream.Stream;

// basic functionality of the collector
Stream.of("one", "two", "three", "four", "five")
        .collect(Collectors.groupingBy(n -> n.length())); // = {3=[one, two], 4=[four, five], 5=[three]}

In the above code, n -> n.length() is the classifier. The return type is Map<Integer, List<String>> in the line# 5 - 6. The grouping allows having a downstream collector which do the subsequent collection operation. For example,

// basic functionality of the collector
Stream.of("one", "two", "three", "four", "five")
        .collect(Collectors.groupingBy(n -> n.length()
                , Collectors.counting())); // = {3=2, 4=2, 5=1}

In the above code, counting operation calculates the number of items. The partitioning is to accept the predicate classifier to classify items based on true or false only. For example,

Stream.of("one", "two", "three", "four", "five")
        .collect(Collectors.partitioningBy(n -> n.length() > 3)); // = {false=[one, two], true=[three, four, five]}

This blog was created as a result of studying the Quiz Yourself: Using Collectors (Advanced). Lets see the advance part.

Grouping, mapping, partitioning and joining

One of the most useful operations are grouping and joining. For example, we have a set of employees (Emp) and Departments (Dept).

  • Group the users into departments they are belongs to
  • List all the users of that department
import java.util.List;
import java.util.stream.Collectors;

    class Dept {
    private int deptId;
    private String deptName;

    public Dept(int deptId, String deptName) {
        this.deptId = deptId;
        this.deptName = deptName;
    }

    public int getDeptId() {
        return deptId;
    }

    public String getDeptName() {
        return deptName;
    }
}

class Emp {
    private Dept dept;
    private String empName;

    public Emp(Dept dept, String empName) {
        this.dept = dept;
        this.empName = empName;
    }

    public Dept getDept() {
        return dept;
    }

    public String getEmpName() {
        return empName;
    }
}

var acc = new Dept(1,"Account");
var digital = new Dept(2,"Digital");
var marketing = new Dept(3,"Marketing");

var employees = List.of(
        new Emp(acc, "Acc-1"),
        new Emp(acc, "Acc-2"),
        new Emp(digital, "Digital-1"),
        new Emp(marketing, "m-1"),
        new Emp(marketing, "m-2"),
        new Emp(marketing, "m-3")
);

// Group employees by department
employees.stream()
    .collect(Collectors.groupingBy(Emp::getDept))
         .forEach((d,l) -> System.out.println(d.getDeptName()+" => "+
                 l.stream()
                 .map(Emp::getEmpName)
                 .collect(Collectors.joining(", "))
         )
 );

// output
// ======
// Marketing => m-1, m-2, m-3
// Account => Acc-1, Acc-2
// Digital => Digital-1

As shown in the above example, as the first step in line# 55, group the employes by their departments. The grouping returns the Dept as key and List of employees as value. In line# 57 - 59, List of the employees for a particular department are join using the comma separator. You can do more advanced grouping as follows:

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

class Student {
    private String name;
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}


var s = List.of(
        new Student("test1", 17),
        new Student("test2", 24),
        new Student("test", 25),
        new Student("test", 26)).stream();

s.collect(Collectors.groupingBy(
        a -> a.getAge() >= 18,
        Collectors.mapping(
                Student::getName,
                Collectors.counting())))
        .forEach((c, d) -> System.out.println(c+" = "+d));

// output
// ======
// false = 1
// true = 3

As shown in line# 31 - 33, Collectors count the number of student names in the mapping function. According to the lambda predicate classifier in line# 30. There are two downstream collectors chained to perform the further operations of mapping and counting.

More simply you can do

s.collect(Collectors.groupingBy(
            a -> a.getAge() >= 18,
    Collectors.counting()))
    .forEach((c, d) -> System.out.println(c+" = "+d));

Same output without mapping. Very similar thing we can do with the partitioningBy method.

You can replace the above line# 1 using the following partitioningBy method.

s.collect(Collectors.partitioningBy(
            a -> a.getAge() >= 18, 
    Collectors.counting()))
    .forEach((c, d) -> System.out.println(c+" = "+d));

The partitionBu is the best in this situation, becase its support two classifications base on the predicate, and ceate two classifications: true and false.

Comments

Popular posts from this blog

How To: GitHub projects in Spring Tool Suite

Spring 3 Part 7: Spring with Databases

Parse the namespace based XML using Python