Из двух методов сбора данных интерфейсов java.util.stream.Stream один из них плохо сконструирован?

В интерфейсе java.util.stream.Stream,

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

объединитель является BiConsumer<R, R>тогда как в

<R, A> R collect(Collector<? super T, A, R> collector);

объединитель является BinaryOperator<A> что ничего, кроме BiFunction<A,A,A>,

В то время как более поздняя форма четко определяет, что будет ссылкой на объединенный объект после объединения, прежняя форма не делает.

Так как же любая библиотека реализации Stream узнает, что такое объединенный объект в первом случае?

3 ответа

Решение

В Java 9 документация Stream.collect(Supplier, BiConsumer, BiConsumer)Метод был обновлен, и теперь он явно упоминает, что вы должны сложить элементы из второго контейнера результатов в первый:

combiner - ассоциативная, не создающая помех функция без сохранения состояния, которая принимает два контейнера частичных результатов и объединяет их, что должно быть совместимо с функцией аккумулятора. Функция объединения должна складывать элементы из второго контейнера результатов в первый контейнер результатов.

(Акцент мой).

collect метод должен использоваться следующим образом:

ArrayList<Integer> collected = Stream.of(1,2,3)
    .collect(
        ArrayList::new, 
        ArrayList::add, 
        ArrayList::addAll);
System.out.println(collected);

Первый аргумент - это поставщик, который предоставляет пустой список массивов для добавления собранных данных. Второй аргумент - это биконсумер, который потребляет каждый элемент массива. Третий аргумент существует только для обеспечения поддержки параллелизма. Это позволяет ему одновременно собирать элементы в несколько списков массивов и запрашивает способ соединения всех этих списков массивов в конце.

Почему collect узнать результат комбинации, если вы не вернете список массивов с добавленным элементом? Ну это потому что ArrayListс изменчивым. Где-то в реализации это вызывает accumulator.accept:

// not real code, for demonstration purposes only
accumulator.accept(someArrayList, theNextElement);

someArrayList сохранит все изменения, внесенные в него после accept возвращается!

Давайте поместим это в более знакомый сценарий,

ArrayList<Integer> list = new ArrayList(Arrays.asList(1,2,3));
doSomething(list);
System.out.println(list); // [1, 2, 3, 4]

private static void doSomething(ArrayList<Integer> list) {
    list.add(4);
}

Даже если doSomething не возвращает новый список массивов, list все еще мутирован. То же самое происходит с BiConsumer.accept, Это вызывает collect "знать", что вы сделали со списком массивов.

combiner используется только в параллельном потоке для объединения 2 результатов, вычисленных в потоках.

На самом деле, потоковое использование Consumer Накапливать результаты приходит из потоков. result сохраняется в Consumerи, наконец, объединить частичный результат от другого Consumer,

Для BinaryOperator комбинер больше похож на код, как показано ниже:

T[] partials = the result is computed in threads...
T result = supplier.get();
for (T partial : partials)
     result = combiner.apply(result, partial)
return result;

Для BiConsumer комбинер больше похож на код ниже:

T[] partials = the result is computed in threads...
T result = supplier.get();
for (T partial : partials)
     combiner.accept(result, partial)
return result;

Из описания пакета потока:

Как и в случае с Reduce(), выгода от выражения collect этот абстрактный способ заключается в том, что он непосредственно поддается распараллеливанию: мы можем накапливать частичные результаты параллельно, а затем объединять их, пока функции накопления и объединения удовлетворяют соответствующим требованиям. Например, чтобы собрать представления String элементов в потоке в ArrayList, мы могли бы написать очевидную последовательную форму for-each:

 ArrayList<String> strings = new ArrayList<>();
 for (T element : stream) {
     strings.add(element.toString());
 }

Или мы можем использовать распараллеленную форму сбора:

 ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
                                            (c, e) -> c.add(e.toString()),
                                            (c1, c2) -> c1.addAll(c2));
//  the requirements showing as an example           ---^
Другие вопросы по тегам