Почему объединитель интерфейса коллектора не соответствует перегруженному методу сбора?
В интерфейсе 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(…))
.