Java 8 уменьшает BinaryOperator для чего используется?

Сейчас я читаю O'reilly Java 8 Lambdas — действительно хорошую книгу. я столкнулся с таким примером.

у меня есть

private final BiFunction<StringBuilder,String,StringBuilder>accumulator=
(builder,name)->{if(builder.length()>0)builder.append(",");builder.append("Mister:").append(name);return builder;};

final Stream<String>stringStream = Stream.of("John Lennon","Paul Mccartney"
,"George Harrison","Ringo Starr");
final StringBuilder reduce = stringStream
    .filter(a->a!=null)
    .reduce(new StringBuilder(),accumulator,(left,right)->left.append(right));
 System.out.println(reduce);
 System.out.println(reduce.length());

это дает правильный результат.

Mister:John Lennon,Mister:Paul Mccartney,Mister:George Harrison,Mister:Ringo Starr

мой вопрос касается метода reduce, последний параметр которого является BinaryOperator

мой вопрос, для чего используется этот параметр? если я изменю на

.reduce(new StringBuilder(),accumulator,(left,right)->new StringBuilder());

вывод будет таким же, если я передам NULL, тогда будет возвращен NPE.

для чего используется этот параметр?

ОБНОВЛЕНИЕ

почему, если я запускаю его на parallelStream, я получаю разные результаты?

первый забег.

returned StringBuilder length = 420

второй запуск

returned StringBuilder length = 546

третий запуск

returned StringBuilder length = 348

и так далее? почему это... не должно возвращать все значения на каждой итерации?

любая помощь очень благодарна.

Благодарю.


person chiperortiz    schedule 01.06.2014    source источник


Ответы (2)


Метод reduce в интерфейсе Stream перегружен. Параметры метода с тремя аргументами:

  • личность
  • аккумулятор
  • объединитель

combiner поддерживает параллельное выполнение. По-видимому, он не используется для последовательных потоков. Однако такой гарантии нет. Если вы измените свои потоки на параллельный поток, я думаю, вы увидите разницу:

Stream<String>stringStream = Stream.of(
    "John Lennon", "Paul Mccartney", "George Harrison", "Ringo Starr")
    .parallel();

Вот пример того, как combiner можно использовать для преобразования последовательного сокращения в сокращение, поддерживающее параллельное выполнение. Есть поток с четырьмя String, а acc используется как аббревиатура для accumulator.apply. Тогда результат редукции можно вычислить следующим образом:

acc(acc(acc(acc(identity, "one"), "two"), "three"), "four");

С совместимым combiner приведенное выше выражение может быть преобразовано в следующее выражение. Теперь можно выполнять два подвыражения в разных потоках.

combiner.apply(
    acc(acc(identity, "one"), "two"),
    acc(acc(identity, "three"), "four"));

Что касается вашего второго вопроса, я использую упрощенный accumulator, чтобы объяснить проблему:

BiFunction<StringBuilder,String,StringBuilder> accumulator =
    (builder,name) -> builder.append(name);

Согласно Javadoc для Stream::reduce, accumulator должен быть ассоциативный. В данном случае это будет означать, что следующие два выражения возвращают один и тот же результат:

acc(acc(acc(identity, "one"), "two"), "three")  
acc(acc(identity, "one"), acc(acc(identity, "two"), "three"))

Это не относится к вышеупомянутому accumulator. Проблема в том, что вы изменяете объект, на который ссылается identity. Это плохая идея для операции reduce. Вот две альтернативные реализации, которые должны работать:

// identity = ""
BiFunction<String,String,String> accumulator = String::concat;

// identity = null
BiFunction<StringBuilder,String,StringBuilder> accumulator =
    (builder,name) -> builder == null
        ? new StringBulder(name) : builder.append(name);
person nosid    schedule 01.06.2014
comment
спасибо nosid У меня есть вопрос, почему я получаю разные результаты на каждой итерации, я думаю, это для распараллеливания... почему несколько результатов используют код имени?? пожалуйста, смотрите мой отредактированный вопрос. - person chiperortiz; 01.06.2014
comment
@chiperortiz: я обновил свой ответ относительно вашего второго вопроса. Пример действительно взят из книги? В этом случае фраза хорошая книга кажется сомнительной. - person nosid; 01.06.2014
comment
в книге более сложный пример и используется только последовательный поток. - person chiperortiz; 01.06.2014
comment
я буду использовать вашу BiFunction, но должен ли я использовать тот же BinaryOperator? - person chiperortiz; 01.06.2014

ответ nosid в основном правильный (+1), но я хотел подчеркнуть конкретный момент.

Параметр identity для reduce должен быть значением идентификатора. Это нормально, если это объект, но если это так, он должен быть неизменным. Если объект «удостоверение» видоизменен, это уже не тождество! Для более подробного обсуждения этого вопроса см. мой ответ на связанный вопрос.

Похоже, что этот пример взят из примера 5-19 Ричарда Уорбертона, Java 8 Lambdas, O'Reilly 2014. Если это так, мне придется поговорить об этом с добрым доктором Уорбертоном.

person Stuart Marks    schedule 01.06.2014
comment
Точно так же параметр BinaryOperator для сокращения должен быть ассоциативным. В противном случае вы получите тарабарские результаты параллельно. - person Brian Goetz; 02.06.2014
comment
пример Ричарда используется в последовательном потоке, а не в параллельном потоке, Стюарт, спасибо за ваш ответ... - person chiperortiz; 02.06.2014
comment
@chiperortiz Действительно, пример последовательный, но для кода неправильно выдавать правильные результаты последовательно и неправильные результаты параллельно, особенно в книге, которая пытается объяснить этот материал. (Кроме того, я подозреваю, что даже последовательный код нарушает некоторые ограничения, и просто повезло, что он дает правильный результат.) - person Stuart Marks; 02.06.2014