Java 12 : The Teeing Collector

Java 12 : The Teeing Collector

Taking the comfort of streams ahead Java 12 has added a new collector feature that merges the results of 2 collectors. Let’s say for an example that you wanted to extract 2 different results from the very same Stream. We would have to create a separate stream and then extract those results individually and move ahead.

The Teeing Collector makes this task a lot easier and enables us to do it in one shot.

As per the official Java documentation a Teeing Collector :

Returns a Collector that is a composite of two downstream collectors. Every element passed to the resulting collector is processed by both downstream collectors, then their results are merged using the specified merge function into the final result

It has the following signature :

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)

Let us take an example to explore it further.

A Use Case:

Let us say we have a List of Students available with us. The Students enroll for Subjects (Physics, Chemistry, Maths) in our current example.

Let’s say we have a requirement where we want to calculate the Total Marks obtained by each student as well as the highest marks in a Subject obtained by each individual Student.

We can very well use the Collectors Teeing functionality here and obtain the result at 1 go. Let us see the code snippets :

Student.java

package com.kapil.concurrency;

import java.util.List;
import java.util.Objects;

public class Student {
    private Long studentId;
    private String firstName;
    private String lastName;

    List<Subject> subjects;

    private Student() { }

    public Student(Long studentId, String firstName, String lastName) {
        this.studentId = studentId;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Long getStudentId() {
        return studentId;
    }

    public void setStudentId(Long studentId) {
        this.studentId = studentId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public List<Subject> getSubjects() {
        return subjects;
    }

    public void setSubjects(List<Subject> subjects) {
        this.subjects = subjects;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return studentId.equals(student.studentId) &&
                firstName.equals(student.firstName) &&
                lastName.equals(student.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, firstName, lastName);
    }
}

Subject.java

package com.kapil.concurrency;

import java.util.Objects;

public class Subject {
    private String subjectName;
    private Integer marks;

    private Subject() {}

    public Subject(String subjectName, Integer marks) {
        this.subjectName = subjectName;
        this.marks = marks;
    }

    public String getSubjectName() {
        return subjectName;
    }

    public void setSubjectName(String subjectName) {
        this.subjectName = subjectName;
    }

    public Integer getMarks() {
        return marks;
    }

    public void setMarks(Integer marks) {
        this.marks = marks;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Subject subject = (Subject) o;
        return subjectName.equals(subject.subjectName) &&
                marks.equals(subject.marks);
    }

    @Override
    public int hashCode() {
        return Objects.hash(subjectName, marks);
    }
}

TeeingSample.java

package com.kapil.concurrency;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TeeingSample {
    public static void main(String[] args) {
        // initialize the student objects
        Student student1 = new Student(1L, "Amar" , "Kumar");
        Student student2 = new Student(2L, "Ankit" , "P");
        Student student3 = new Student(3L, "Manas" , "J");

        List<Subject> student1Subjects = List.of(new Subject("Physics", 90), new Subject("Chemistry", 88), new Subject("Maths", 92));
        List<Subject> student2Subjects = List.of(new Subject("Physics", 77), new Subject("Chemistry", 88), new Subject("Maths", 66));
        List<Subject> student3Subjects = List.of(new Subject("Physics", 89), new Subject("Chemistry", 90), new Subject("Maths", 89));

        student1.setSubjects(student1Subjects);
        student2.setSubjects(student2Subjects);
        student3.setSubjects(student3Subjects);

        List<Student> studentList = List.of(student1, student2, student3);

        // let's say we want to find the total marks and highest marks of each individual student.
        // if we use plain vanilla Streams API, we would have to run 2 iteration to get that.
        // using Teeing we can do it at 1 go.
        // for demo only // store data as <Student name, total marks(max marks)>
        Map<String, String> studentMarksMap = new HashMap<>();
        studentList.stream().forEach(student -> {
            student.getSubjects().stream().collect(
                    Collectors.teeing(
                            Collectors.summingInt(Subject::getMarks),
                            Collectors.maxBy(Comparator.comparing(Subject::getMarks)),
                            (collector1, collector2) -> {
                                studentMarksMap.put(student.getFirstName() +   " " +student.getLastName(), collector1 + "(" + collector2.get().getMarks() + ")");
                                return studentMarksMap;
                            }
                    )
            );
        });
        System.out.println(studentMarksMap);
    }
}

Output:

TeeingSample Output

Thus we can from the above example that we did not have to create 2 different Streams to extract the maximum marks and the sum of the marks for each Student.

Teeing can be very handy when we want to perform multiple Aggregate functions like ,sum, min, max, etc at one go.

Thus we see whenever we want to perform stream() operation twice to get 2 different result from the same collection , we can avoid that and use Collectors.teeing() instead.

So that’s it for this post. Go ahead and run this piece of code in your favorite java editor.
See you until next time. Happy Learning !

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top