Почему объединитель интерфейса коллектора не соответствует перегруженному методу сбора?

В интерфейсе Stream<T> имеется метод перегрузки collect() со следующей сигнатурой:

<R> R collect(Supplier<R> supplier,
          BiConsumer<R,? super T> accumulator,
          BiConsumer<R,R> combiner)

Существует еще одна версия collect(Collector<? super T,A,R> collector), которая получает объект с тремя предыдущими функциями. Свойство интерфейса Collector, соответствующее combiner, имеет подпись BinaryOperator<A> combiner().

В последнем случае Java API 8 утверждает, что:

Функция объединителя может складывать состояние из одного аргумента в другой и возвращать его или возвращать новый контейнер результатов.

Почему первый метод collect не получает также BinaryOperator<R>?

Ответы

Ответ 1

Версия inline (3-arg) версии collect предназначена для тех случаев, когда у вас уже есть эти функции, "лежащие". Например:

ArrayList<Foo> list = stream.collect(ArrayList::new, 
                                     ArrayList::add,
                                     ArrayList::addAll);

или

BitSet bitset = stream.collect(BitSet::new, 
                               BitSet::set,
                               BitSet::or);

Хотя это всего лишь мотивирующие примеры, наши исследования с аналогичными существующими классами строителей заключались в том, что подписи существующих кандидатов-сумматоров больше подходят для преобразования в BiConsumer, чем для BinaryOperator. Предлагая "гибкость", которую вы просите, это сделает эту перегрузку гораздо менее полезной в тех случаях, когда она была разработана для поддержки - это когда у вас уже есть функции, лежащие вокруг, и вы не хотите делать ( или узнать о создании) Collector, чтобы собрать их.

Коллекционер, с другой стороны, имеет гораздо более широкий диапазон применений и, следовательно, заслуживает дополнительной гибкости.

Ответ 2

Если первый метод collect получит BinaryOperator<R>, то следующий пример не будет компилироваться:

ArrayList<Foo> list = stream.collect(ArrayList::new, 
                                     ArrayList::add,
                                     ArrayList::addAll);

В этом случае компилятор не смог вывести возвращаемый тип combiner и дал бы ошибку компиляции.

Итак, если эта версия метода collect была совместима с интерфейсом Collector, это способствовало бы более сложному использованию этой версии метода collect, который не был предназначен.

Ответ 3

Имейте в виду, что основной целью Stream.collect() является поддержка Mutable Reduction. Для этой операции обе функции, аккумулятор и сумматор предназначены для манипулирования изменяемым контейнером и не должны возвращать значение.

Поэтому гораздо удобнее не настаивать на возврате значения. Как отметил Брайан Гетц, это решение позволяет повторно использовать множество существующих типов контейнеров и их методов. Без возможности использовать эти типы напрямую, весь метод tr-arg collect был бы бессмысленным.

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

Конечно, он не предназначен для использования stream.collect(Collectors.reducing(…)) вместо stream.reduce(…). Вместо этого эта абстракция удобна при объединении коллекторов, например. например groupingBy(…,reducing(…)).