Java - Using Collectors.teeing() Examples

This tutorial shows you how to use Collectors.teeing which works in Java 12 or above.

You may have been familiar with Collector in Java. Collector is usually used to process each element into an accumulated result. There are some static methods for creating Collector such as toMap, toList, and groupingBy. What if you need to process each element using more than one downstream collectors.

Since Java 12, there is a static method of Collector that passes each element to two downstream collectors, then merges the results of both collectors using a specified merge function. You can use Collectors.teeing for that purpose. Below are the examples.

Using Collectors.teeing

Here's the method to be used.

  public static <T, R1, R2, R> Collector<T, ?, R> teeing(
      Collector<? super T, ?, R1> downstream1,
      Collector<? super T, ?, R2> downstream2,
      BiFunction<? super R1, ? super R2, R> merger
  )

For using Collectors.teeing, you need to pass two collectors as the first and second arguments. Each element will be processed by both collectors. For merging the results, you need to pass a BiFunction as the third argument which acts as a merge function. Inside the merge function, you can use the values from both collectors, then return any type of output.

Below are some usage examples of Collectors.teeing.

Getting Average of Numbers

In the first example, we are going to get the average value of numbers. The first downstream collectors summingDouble is used to summarize all values. The second downstream collector counting is for counting the number of elements. The merge function returns the average by dividing the value from the first downstream (sum) by the value from the second downstream (count).

  double average = Stream.of(1, 2, 3, 4, 5, 6)
      .collect(Collectors.teeing(
          Collectors.summingDouble(i -> i),
          Collectors.counting(),
          (sum, count) -> sum / count
      ));
  
  System.out.println(average);

Output:

  3.5

 

Getting Minimum and Maximum Elements

For the second example, we use a class named Item and a List containing some instances of Item. Using Collectors.teeing, we are going to find the cheapest and the most expensive items. The first downstream collector minBy is used to get the minimum value, while the second downstream collector is used to get the maximum value.

  @AllArgsConstructor
  @Getter
  @Setter
  public static class Item {

    private int id;

    private String name;

    private int price;
  }
  List<Item> ItemList = List.of(
      new Item(1, "One", 1000),
      new Item(2, "Two", 2500),
      new Item(3, "Three", 500),
      new Item(4, "Four", 1200)
  );
  Map<String, Item> result = ItemList.stream()
      .collect(
          Collectors.teeing(
              Collectors.minBy(Comparator.comparing(Item::getPrice)),
              Collectors.maxBy(Comparator.comparing(Item::getPrice)),
              (e1, e2) -> Map.ofEntries(
                  Map.entry("min", e1.get()),
                  Map.entry("max", e2.get())
              )
          )
      );

  System.out.println("Cheapest item: " + result.get("min").getName());
  System.out.println("Most expensive item: " + result.get("max").getName());

Output:

  Cheapest item: Three
  Most expensive item: Two

 

Filter and Count Matching Elements

For the third example, we are going to use the same list for filtering items whose price is greater than 1000 and counts the number of matching elements at the same time. We need to use Collectors.filtering for both downstreams. For the first downstream, we pass a Predicate as the first argument and Collectors.toList as the second argument. For the first downstream, we pass the same Predicate as the first argument and Collectors.counting as the second argument.

  Predicate<Item> predicate = item -> item.getPrice() > 1000;
  Map<String, Object> result = ItemList.stream()
      .collect(
          Collectors.teeing(
              Collectors.filtering(predicate, Collectors.toList()),
              Collectors.filtering(predicate, Collectors.counting()),
              (items, count) -> Map.ofEntries(
                  Map.entry("items", items),
                  Map.entry("count", count)
              )
          )
      );

  System.out.println("Number of matching items: " + result.get("count"));

  System.out.println("Matching items:");
  List<Item> items = (List<Item>) result.get("items");
  for (Item item : items) {
    System.out.println(item.getName());
  }
}

Output:

  Number of matching items: 2
  Matching items:
  Two
  Four

 

That's how to use Collectors.teeing. Make sure you use at least JDK 12 or above for using it.