Java 12 - Stream API Collectors.teeing()

Java 12 added a new static method teeing for Collectors that accepts two collectors and a function to merge their results.

Every element passed to the resulting collector is processed by both downstream collectors (downstream1 and downstream2), then their results are merged using the specified merge function (merger) into the final result.

TeeingCollector.java
import java.util.List;
import java.util.Arrays;
import static java.util.stream.Collectors.*;

public class TeeingCollector {

    public static void main(String[] args) {
        // Traditional style:
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        double average = 0d;
        for (Integer number : numbers) {
            average += (((double) number) / numbers.size());
        }
        System.out.println(average);

        // Using Collectors.teeing:
        average = numbers.stream()
                .collect(teeing(
                        summingDouble(i -> i),
                        counting(),
                        (sum, n) -> sum / n));
        System.out.println(average);
    }
}
                    

In above example every numbers is processed by summingDouble and counting. The final result sum/n are merged in the function. And the output from both "traditional style" and using Collectors.teeing are:

5.0
5.0

Another example, I'll demonstrate the use stream filtering to get downstream collectors. In this case, I'm filtering cities in "Asia" with cost of living less (or equals) to 1000$

TeeingCollectorUseCase.java
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;

public class TeeingCollectorUseCase {

    public static void main(String[] args) {
        var result = Stream.of(
                // City(String name, String region, Integer costOfLiving)
                new City("Colombo", "South Asia", 987),
                new City("Da Lat", "South East Asia", 914),
                new City("Kiev", "Eastern Europe", 1334),
                new City("Melbourne", "Australia", 3050),
                new City("Shanghai", "East Asia", 1998),
                new City("Taghazout", "North Africa", 1072),
                new City("Ubud", "South East Asia", 1331))
                .collect(Collectors.teeing(
                        // first collector, select cities in Asia 
                        // with monthly cost of living less than 1000$
                        Collectors.filtering(
                                o -> ((City) o).region.contains("Asia") && 
                                        ((City) o).costOfLiving <= 1000,
                                // collect the name in a list
                                Collectors.mapping(o -> ((City) o).name, Collectors.toList())),
                        // second collector, count the number of those cities
                        Collectors.filtering(
                                o -> ((City) o).region.contains("Asia") &&
                                        ((City) o).costOfLiving <= 1000,
                                counting()),
                        // merge the collectors, put into a String
                        (l, c) -> "Result[cities=" + l + ", count=" + c + "]"));

        System.out.println(result);
        // Result[cities=[Colombo, Da Lat], count=2]
    }

    static class City {

        private final String name;
        private final String region;
        private final Integer costOfLiving;

        public City(String name, String region,
                Integer costOfLiving) {
            this.name = name;
            this.region = region;
            this.costOfLiving = costOfLiving;
        }

        public String getRegion() {
            return region;
        }

        public Integer getCostOfLiving() {
            return costOfLiving;
        }
    }
}
                    

And from sample list, we get Colombo and Dalat:

Result[cities=[Colombo, Da Lat], count=2]