Эквивалент 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
.