Lesson 15: New Collectors Method – teeing() (Java 12+)

Java 12 introduced the Collectors.teeing() method, which allows a single stream to be processed by two different collectors simultaneously, and then combines their results into a final output.


1. What is Collectors.teeing()?

teeing() runs two collectors at the same time on a stream.
The results from both collectors are combined using a third function.
Useful for performing multiple aggregations in a single pass over the data.


2. Syntax of Collectors.teeing()

Collectors.teeing(
    Collector<T, ?, R1> collector1,  // First collector
    Collector<T, ?, R2> collector2,  // Second collector
    BiFunction<R1, R2, R> merger     // Function to combine results
)

Processes elements once, reducing computational overhead.


3. Example: Finding the Average and Sum in One Pass

📌 Problem: Given a list of numbers, calculate:

  1. The sum of all numbers.
  2. The average of all numbers.
  3. Return both values as a String.

🔹 Java 12+ Solution Using teeing()

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

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(10, 20, 30, 40, 50);

        String result = numbers.stream()
            .collect(Collectors.teeing(
                Collectors.summingInt(n -> n),         // Collector 1: Sum
                Collectors.averagingInt(n -> n),       // Collector 2: Average
                (sum, avg) -> "Sum: " + sum + ", Average: " + avg // Merge results
            ));

        System.out.println(result);
    }
}

Output:

Sum: 150, Average: 30.0

No need to process the list twice!


4. When Should You Use teeing()?

🚀 Use teeing() when:

  1. You need to compute two independent results from a single stream.
  2. You want to combine those results into a single output.
  3. You want to improve performance by avoiding multiple iterations.

📌 Example: Finding the Min and Max in One Pass

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

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(5, 2, 8, 1, 9);

        String result = numbers.stream()
            .collect(Collectors.teeing(
                Collectors.minBy(Integer::compare),  // Find Min
                Collectors.maxBy(Integer::compare),  // Find Max
                (min, max) -> "Min: " + min.get() + ", Max: " + max.get() // Combine
            ));

        System.out.println(result);
    }
}

Output:

Min: 1, Max: 9

Faster than processing the list twice!


5. Comparison: Before Java 12 vs. Java 12+ (teeing())

FeatureBefore Java 12 (Multiple Passes)Java 12+ (teeing())
Computing sum and averageTwo separate .collect() callsSingle .collect(teeing()) call
Finding min and maxTwo separate .min() and .max() callsSingle .collect(teeing()) call
PerformanceIterates the stream twiceProcesses the stream once

Lesson Reflection

  1. Why is Collectors.teeing() better than calling two separate collect() methods?
  2. Can you think of another use case where teeing() would be helpful?
  3. What happens if one of the collectors in teeing() returns an empty result?

🔹 What If You Need More Than Two Collectors?

If you need more than two collectors, you have to nest teeing() calls or use collect(Collectors.toMap()) instead.

📌 Example: Using Nested teeing() for Three Collectors

🚀 Problem: Find sum, average, and count in a single stream pass.

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

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(10, 20, 30, 40, 50);

        var result = numbers.stream()
            .collect(Collectors.teeing(
                Collectors.summingInt(n -> n),  // Sum
                Collectors.teeing(
                    Collectors.averagingInt(n -> n), // Average
                    Collectors.counting(),           // Count
                    (avg, count) -> "Average: " + avg + ", Count: " + count
                ),
                (sum, avgCount) -> "Sum: " + sum + ", " + avgCount
            ));

        System.out.println(result);
    }
}

Output:

Sum: 150, Average: 30.0, Count: 5

We nested one teeing() inside another to use three collectors.


🔹 Alternative: Using collect(Collectors.toMap())

If you need more than two aggregations, another option is using a Map:

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

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(10, 20, 30, 40, 50);

        Map<String, Object> result = numbers.stream()
            .collect(Collectors.toMap(
                n -> "Result",
                n -> Map.of(
                    "Sum", numbers.stream().mapToInt(i -> i).sum(),
                    "Average", numbers.stream().mapToInt(i -> i).average().orElse(0),
                    "Count", numbers.size()
                ),
                (a, b) -> b // Merge function (keeps last value)
            ));

        System.out.println(result.get("Result"));
    }
}

Output:

{Sum=150, Average=30.0, Count=5}

toMap() is a flexible alternative when you need multiple aggregations.


🔍 Key Takeaways

teeing() only supports two collectors.
For more than two collectors, nest teeing() or use toMap().
Use teeing() when you need just two aggregations; use toMap() for more flexibility.

The next Java 13+ feature: Text Blocks (""" Multiline Strings)? 😊🚀

Tags:

Java Sleep