[Перевод] Новое в Java 12: The Teeing Collector
В этой статье мы рассмотрим новый коллектор, представленный в 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 Collector 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 }
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;
}
}
class EventParticipation {
private List guestNameList;
private Integer totalNumberOfParticipants;
public EventParticipation(List 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 list1, List 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}
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 в этой презентации.
Успешного коллекционирования!