Java has been striving hard to reduce the verbosity of its code in the latest features. Once such feature introduced is the teeing() method introduced in Java 12.
A tee means this :

Input from two different sources can be combined and fed into another source.
Let’s say you have a list of employees.
You want to retrieve the list of all their names.
You also want to take the total count of male employees.
Can you do this in a single operation in Java?
Yes!
You can do it through Collectors.teeing() introduced in Java 12.
Traditionally you can get both the results through a for loop like this:
Employee employee1 = new Employee("Reshma", "Female");
Employee employee2 = new Employee("Rathish", "Male");
Employee employee3 = new Employee("Kumutha", "Female");
Employee employee4 = new Employee("Rathish", "Male");
Employee employee5 = new Employee("Rodrina", "Female");
Employee employee6 = new Employee("Kalyani", "Female");
List<Employee> employees = Arrays.asList(employee1, employee2, employee3, employee4, employee5, employee6);
// USING TRADITIONAL FOR LOOP
int maleCount = 0;
List<String> names = new ArrayList<String>();
for (Employee employee : employees) {
if (employee.getGender().equals("Male")) {
maleCount++;
}
names.add(employee.getName());
}
System.out.println("Male Count: " + maleCount + " Employee names: " + names);
It took two variable declarations , a for loop , an if loop , increment operation , manual addition of names to a list and finally a print operation.
Using streams and without Collectors.teeing() method it could be achieved like this:
Long maleEmployeesCount = employees.stream().filter(e -> e.getGender().equals("Male"))
.collect(Collectors.counting());
List<String> employeeNames = employees.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println("Male Count: " + maleEmployeesCount + " Employee names: " + employeeNames);
In the above code first, the employee list is converted to a stream using stream() method and then applied the filter() operation to filter male employees and the collect() method is called using Collectors.counting() method as a parameter to find their count.
And then again a new stream is created and the names are mapped out and collected in a list.
Using streams took less lines of code and no loops.
We can avoid the second streaming using Collectors.teeing() like this:
var result = employees.stream().collect(
Collectors.teeing(
Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting()),
Collectors.mapping(e -> e.getName(), Collectors.toList()),
(mCount, nameList) -> {
return "Male Count: " + mCount + " Employee names: " + nameList;
}
));
System.out.println(result);
Though a longer line , it took a single line of code to compute both the results.
Lets look at the code in details.
The employee list is first converted into a stream and then collect method is called on it:
var result = employees.stream.collect(--pass a collector --here);
the collect method expects a collector and we pass the teeing collector here:
var result = employees.stream.collect(Collectors.teeing(--pass arguments here--));
The Collectors.teeing method takes in three arguments:
- The first collector which performs the first operation (finding out the total count of male employees in our case)
- The second collector which performs the second operation (finding out the list of employee names in our case)
- A BiFunctional interface which takes two arguments (the outputs of the above two collectors) and returns a result (computed through lamda expression)
Here is the first collector passed :
Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting())
This does the same thing as shown in the previous example using the first stream: finds the count of male employees and returns them as a collector.
Below is the second collector passed:
Collectors.mapping(e -> e.getName(), Collectors.toList())
This does the same things as shown in the previous example using the second stream : collect all names in a list and return them as a collector.
Below is the lamda expression passed as a third argument :
(mCount, nameList) -> {
return "Male Count: " + mCount + " Employee names: " + nameList;
}
This just collects the output from the first two collectors and concatenates them in a string. We could do more things with them and fully utilize the power of teeing().
All the above three types of code(traditional for loop, streams without teeing() , streams with teeing()) produce the same output:
Male Count: 2 Employee names: [Reshma, Rathish, Kumutha, Rathish, Rodrina, Kalyani]
Three different ways to solve the same problem!
Here is the full code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class Client {
public static void main(String a[]) {
Employee employee1 = new Employee("Reshma", "Female");
Employee employee2 = new Employee("Rathish", "Male");
Employee employee3 = new Employee("Kumutha", "Female");
Employee employee4 = new Employee("Rathish", "Male");
Employee employee5 = new Employee("Rodrina", "Female");
Employee employee6 = new Employee("Kalyani", "Female");
List<Employee> employees = Arrays.asList(employee1, employee2, employee3, employee4, employee5, employee6);
// USING TRADITIONAL FOR LOOP
int maleCount = 0;
List<String> names = new ArrayList<String>();
for (Employee employee : employees) {
if (employee.getGender().equals("Male")) {
maleCount++;
}
names.add(employee.getName());
}
System.out.println("Male Count: " + maleCount + " Employee names: " + names);
// USING STREAMS AND COLLECTOR API (WITHOUT TEEING)
Long maleEmployeesCount = employees.stream().filter(e -> e.getGender().equals("Male"))
.collect(Collectors.counting());
List<String> employeeNames = employees.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println("Male Count: " + maleEmployeesCount + " Employee names: " + employeeNames);
// USING STREAMS AND COLLECTOR TEEING.
var result = employees.stream().collect(
Collectors.teeing(
Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting()),
Collectors.mapping(e -> e.getName(), Collectors.toList()),
(mCount, nameList) -> {
return "Male Count: " + mCount + " Employee names: " + nameList;
}
));
System.out.println(result);
}
}
The Employee class:
public class Employee {
private String name;
private String gender;
public Employee(String name, String gender) {
this.name = name;
this.gender = gender;
}
@Override
public String toString() {
return "Employee [name=" + name + ", gender=" + gender + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
Teeing() makes java code less verbose and saves time typing.
Leave a Reply