Pull to refresh

Новое в Java 12: The Teeing Collector

Reading time4 min
Views12K
Original author: Marco Molteni
В этой статье мы рассмотрим новый коллектор, представленный в Java 12. Эта новая функция не была анонсирована в официальном JEP, поскольку это был минорный change request с заголовком "Create Collector, which merges the results of two other collectors". Она предназначена для объединения результатов с двух коллекторов.


Все интересное — под катом


Если вы еще не знаете, что такое коллекторы
Коллекторы, это специальные классы, использующиеся для преобразования стрима в другую структуру данных. Например, в list:
list = Stream.of(1,2,3).collect(Collectors.toList());
//в листе теперь находятся элементы 1, 2 и 3

Это бесполезный пример, потому что он создает новый стрим и сразу его преобразует. Он придуман для того, чтобы показать использование коллекторов

Документация


Нажмите сюда, чтобы просмотреть Collectors#teeing. Согласно официальной документации:
«… возвращает коллектор, составленный из двух нижестоящих коллекторов. Каждый элемент переданный в результирующий коллектор, обрабатывается обоими нижестоящими коллекторами, а затем их результаты объединяются с помощью специальной функции, которая соединяет их в конечный результат.»
Оригинал
"...returns a Collector that is a composite of two downstream collectors. Every element passed to the resulting collector is processed by both downstream collectors, then their results are merged using the specified merge function into the final result."
Заголовок метода:

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)

Интересный факт


Это тройник(teeing с англ.):



Teeing произошел от тройника. Согласно Википедии, «тройник — самый распространенный фитинг(соединительная часть трубопровода, прим. переводчика) используемый для объединения[или разделения] потока жидкостей(в данном случае имеются ввиду стримы, stream — ручей/поток, прим. переводчика)».
Предлагались и другие имена: bisecting(разделение_на_2_части), duplexing, bifurcate(раздвоение), replicator, fanout(разветвление), tapping, unzipping, collectionToBothAndThen, biCollecting, expanding(расширение), forking, и т.д.
Все альтернативы, оцененные разработчиками Core, можно посмотреть здесь.

Примеры использования


Я собрал три примера использования кода с разными уровнями сложности.

Список гостей


Мы извлекаем два разных типа информации из списка объектов в потоке. Каждый гость должен принять приглашение и может привести семью. Мы хотим знать, кто подтвердил бронирование и общее количество участников(включая гостей и членов семьи).

var result =
  Stream.of(
  // Guest(String name, boolean participating, Integer participantsNumber)
  new Guest("Marco", true, 3),
  new Guest("David", false, 2),
  new Guest("Roger",true, 6))
  .collect(Collectors.teeing(
    // Первый коллектор, мы выбираем только тех, кто подтвердил участие
    Collectors.filtering(Guest::isParticipating,
       // мы хотим взять только имя в списке
       Collectors.mapping(o -> o.name, Collectors.toList())),
       // второй коллектор, мы хотим найти общее количество участников
       Collectors.summingInt(Guest::getParticipantsNumber),
       // мы объединяем коллекторы в новый объект,
       // значения передаются неявно
       EventParticipation::new
   ));
  System.out.println(result);
  // Результат
  // EventParticipation { guests = [Marco, Roger],
  // total number of participants = 11 }


Guest
class Guest {
  private String name;
  private boolean participating;
  private Integer participantsNumber;
  public Guest(String name, boolean participating,
   Integer participantsNumber) {
    this.name = name;
    this.participating = participating;
    this.participantsNumber = participantsNumber;
  }
  public boolean isParticipating() {
    return participating;
  }
  public Integer getParticipantsNumber() {
    return participantsNumber;
  }
}


EventParticipation
class EventParticipation {
  private List<String> guestNameList;
  private Integer totalNumberOfParticipants;
  public EventParticipation(List<String> guestNameList,
   Integer totalNumberOfParticipants) {
    this.guestNameList = guestNameList;
    this.totalNumberOfParticipants = totalNumberOfParticipants;
}
@Override
public String toString() {
  return "EventParticipation { " +
    "guests = " + guestNameList +
    ", total number of participants = " + totalNumberOfParticipants +
    " }";
  }}


Отфильтровать имена в двух разных списках


В этом примере мы разделяем поток имен на два списка в соответствии с фильтром.

var result =
  Stream.of("Devoxx", "Voxxed Days", "Code One", "Basel One",
     "Angular Connect")
  .collect(Collectors.teeing(
  // первый коллектор
  Collectors.filtering(n -> n.contains("xx"), Collectors.toList()),
  // второй коллектор
  Collectors.filtering(n -> n.endsWith("One"), Collectors.toList()),
  // слияние - автоматический вывод типа здесь не работает
  (List<String> list1, List<String> list2) -> List.of(list1, list2)
  ));
  System.out.println(result); // -> [[Devoxx, Voxxed Days], [Code One, Basel One]]

Посчитайте и сложите стрим из чисел


Возможно, вы видели похожий пример, появляющийся в блогах, которые соединяют sum и count для получения среднего. Этот пример не требует Teeing, и вы можете просто использовать AverageInt и простой коллектор.

В следующем примере используются функции из Teeing для возврата двух значений:

var result =
  Stream.of(5, 12, 19, 21)
    .collect(Collectors.teeing(
      // первый коллектор
      Collectors.counting(),
      // второй коллектор
      Collectors.summingInt(n -> Integer.valueOf(n.toString())),
      // объединение: (count, sum) -> new Result(count, sum);
      Result::new
  ));
  System.out.println(result); // -> {count=4, sum=57}

Result
class Result {
  private Long count;
  private Integer sum;
  public Result(Long count, Integer sum) {
    this.count = count;
    this.sum = sum;
  }
  @Override
  public String toString() {
    return "{" +
      "count=" + count +
      ", sum=" + sum +
    '}';
  }}


Возможная ловушка


Map.Entry

Многие примеры используют Map.Entry для хранения результата BiFunction. Пожалуйста, не делайте этого, потому что вы не сможете хранить последний аргумент в Map. В Java Core нет стандартного объекта для хранения двух значений — вы будете должны создать его самостоятельно.

Все о новых фичах Java 12


Вы можете узнать больше информации и интересных фактов о Java 12 в этой презентации.

Успешного коллекционирования!
Tags:
Hubs:
+12
Comments9

Articles