Mastering Java’s Collectors.teeing() with Practical Examples

Introduction to Collectors.teeing()

Java 12 introduced the Collectors.teeing() method, which allows developers to process a stream in two separate ways simultaneously, then merge the results with a BiFunction. This powerful collector is especially useful when you need to aggregate data or calculate multiple values from a single stream pass.

Syntax Overview

The Collectors.teeing() method takes three arguments:

  1. Collector1 - the first collector for partial data processing.

  2. Collector2 - the second collector for partial data processing.

  3. Merger - a BiFunction that combines the results of both collectors into a final output.

Here's the basic syntax:

Stream<T> stream = ...;
Collector<T, ?, R> result = stream.collect(Collectors.teeing(
    Collector1, 
    Collector2, 
    (result1, result2) -> mergeFunction(result1, result2)
));

Practical Examples

Example 1: Calculating Average and Total Simultaneously

Let’s say you have a list of product prices, and you want to calculate both the average and the total price in a single operation.

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

public class TeeingExample {
    public static void main(String[] args) {
        List<Double> prices = List.of(19.99, 9.99, 14.99, 29.99, 24.99);

        var result = prices.stream().collect(Collectors.teeing(
                Collectors.averagingDouble(Double::doubleValue), 
                Collectors.summingDouble(Double::doubleValue),
                (average, sum) -> "Average Price: " + average + ", Total Price: " + sum
        ));

        System.out.println(result); // Output: Average Price: 19.19, Total Price: 99.95
    }
}

In this example:

  • Collector1 computes the average price.

  • Collector2 calculates the sum of prices.

  • Merger combines the average and total into a descriptive string.

Example 2: Finding the Longest and Shortest String in a List

For lists where you need the longest and shortest strings simultaneously, Collectors.teeing() can be handy.

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

public class TeeingStringExample {
    public static void main(String[] args) {
        List<String> words = List.of("stream", "collector", "Java", "teeing", "example");

        var result = words.stream().collect(Collectors.teeing(
                Collectors.maxBy(Comparator.comparingInt(String::length)),
                Collectors.minBy(Comparator.comparingInt(String::length)),
                (longest, shortest) -> "Longest: " + longest.orElse("N/A") + ", Shortest: " + shortest.orElse("N/A")
        ));

        System.out.println(result); // Output: Longest: collector, Shortest: Java
    }
}

Here:

  • Collector1 finds the longest string.

  • Collector2 finds the shortest string.

  • Merger formats the output to display both results together.

Example 3: Counting and Grouping Items by Condition

Imagine you have a list of students with scores, and you want to count how many passed and failed based on a passing threshold.

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

class Student {
    String name;
    int score;

    Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public int getScore() {
        return score;
    }
}

public class TeeingCountExample {
    public static void main(String[] args) {
        List<Student> students = List.of(
            new Student("Alice", 75),
            new Student("Bob", 45),
            new Student("Charlie", 85),
            new Student("Daisy", 55)
        );

        Map<String, Long> result = students.stream().collect(Collectors.teeing(
                Collectors.filtering(s -> s.getScore() >= 60, Collectors.counting()),
                Collectors.filtering(s -> s.getScore() < 60, Collectors.counting()),
                (passed, failed) -> Map.of("Passed", passed, "Failed", failed)
        ));

        System.out.println(result); // Output: {Passed=2, Failed=2}
    }
}

Explanation:

  • Collector1 filters and counts students who passed (score >= 60).

  • Collector2 filters and counts students who failed (score < 60).

  • Merger maps the counts to labels "Passed" and "Failed."

When to Use Collectors.teeing()

Collectors.teeing() is useful when:

  • You need multiple results from a single stream processing pass.

  • You’re aggregating different calculations (e.g., count, sum, average) at once.

  • You’re merging multiple conditions or classifications from a stream.

Limitations and Considerations

  1. Java 12+ Required: Collectors.teeing() is available only from Java 12 onwards.

  2. Performance: While efficient for combined processing, complex operations may need careful testing for performance impacts on large data sets.

  3. Alternative Approaches: For more than two collectors, consider multiple .collect() calls or switch to a Map.

Conclusion

Collectors.teeing() is a powerful tool for combining results of two distinct collectors into one operation. It’s perfect for scenarios where you want to optimize performance by reducing the number of passes over the stream. By mastering collectors teeing(), you can make your Java code cleaner and more efficient when handling multiple results from the same data source.

More such articles:

https://medium.com/techwasti

https://www.youtube.com/@maheshwarligade

https://techwasti.com/series/spring-boot-tutorials

https://techwasti.com/series/go-language

0
Subscribe to my newsletter

Read articles from Maheshwar Ligade directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Maheshwar Ligade
Maheshwar Ligade

Learner, Love to make things simple, Full Stack Developer, StackOverflower, Passionate about using machine learning, deep learning and AI