Эквивалент Scala foldLeft в Java 8

Что эквивалентно Scala great foldLeft в Java 8?

У меня возникло соблазн подумать, что это было reduce, но сокращение должно вернуть что-то одинакового типа в зависимости от того, что оно уменьшает.

Пример:

import java.util.List;

public class Foo {

    // this method works pretty well
    public int sum(List<Integer> numbers) {
        return numbers.stream()
                      .reduce(0, (acc, n) -> (acc + n));
    }

    // this method makes the file not compile
    public String concatenate(List<Character> chars) {
        return chars.stream()
                    .reduce(new StringBuilder(""), (acc, c) -> acc.append(c)).toString();
    }
}

Проблема в приведенном выше коде - это acc umulator: new StringBuilder("")

Таким образом, может ли кто-нибудь указать мне на правильный эквивалент кода foldLeft/fix my?

Ответ 1

Обновление:

Вот начальная попытка исправить ваш код:

public static String concatenate(List<Character> chars) {
        return chars
                .stream()
                .reduce(new StringBuilder(),
                                StringBuilder::append,
                                StringBuilder::append).toString();
    }

Он использует следующий метод уменьшения:

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

Это может показаться запутанным, но если вы посмотрите на javadocs, есть хорошее объяснение, которое может помочь вам быстро понять детали. Сокращение эквивалентно следующему коду:

U result = identity;
for (T element : this stream)
     result = accumulator.apply(result, element)
return result;

Для более подробного объяснения, пожалуйста, проверьте этот источник.

Это использование неверно, потому что оно нарушает договор сокращения, в котором говорится, что аккумулятор должен быть ассоциативной, неинтерферирующей функцией без учета состояния для включения дополнительного элемента в результат. Другими словами, поскольку идентификатор изменен, результат будет нарушен в случае параллельного выполнения.

Как указано в комментариях ниже, правильный вариант использует сокращение следующим образом:

return chars.stream().collect(
     StringBuilder::new, 
     StringBuilder::append, 
     StringBuilder::append).toString();

Поставщик StringBuilder::new будет использоваться для создания контейнеров многократного использования, которые позже будут объединены.

Ответ 2

Метод, который вы ищете, java.util.Stream.reduce, особенно перегрузка с тремя параметрами, идентификацией, аккумулятором и двоичной функцией. Это правильный эквивалент Scala foldLeft.

Однако вам не разрешено использовать Java reduce таким образом, а также не Scala foldLeft. Вместо этого используйте collect.

Ответ 3

В Java 8 Stream API нет эквивалента foldLeft. Как отмечают другие, reduce(identity, accumulator, combiner) близок, но он не эквивалентен foldLeft, потому что он требует, чтобы результирующий тип B соединялся с самим собой и был ассоциативным (другими словами, моноидоподобным), свойство, которое не каждый type есть.

Для этого есть также запрос на улучшение: добавить операцию терминала Stream.foldLeft()

Чтобы понять, почему сокращение не работает, рассмотрите следующий код, где вы намереваетесь выполнить серию арифметических операций, начиная с заданного числа:

val arithOps = List(('+', 1), ('*', 4), ('-', 2), ('/', 5))
val fun: (Int, (Char, Int)) => Int = {
  case (x, ('+', y)) => x + y
  case (x, ('-', y)) => x - y
  case (x, ('*', y)) => x * y
  case (x, ('/', y)) => x / y
}
val number = 2
arithOps.foldLeft(number)(fun) // ((2 + 1) * 4 - 2) / 5

Если вы попытались написать reduce(2, fun, combine), какую функцию объединителя вы могли бы передать, которая объединяет два числа? Добавление двух чисел вместе явно не решает проблему. Кроме того, значение 2 явно не является элементом идентификации.

Обратите внимание, что никакая операция, требующая последовательного выполнения, может быть выражена в терминах reduce. foldLeft на самом деле более общий, чем reduce: вы можете реализовать reduce с foldLeft, но вы не можете реализовать foldLeft с помощью reduce.