How and why to use Collectors.teeing() method in Java?

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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s