Кажется, у меня проблемы с пониманием того, как Java объединяет потоковые операции в потоковый конвейер.
При выполнении следующего кода
public
static void main(String[] args) {
StringBuilder sb = new StringBuilder();
var count = Stream.of(new String[]{"1", "2", "3", "4"})
.map(sb::append)
.count();
System.out.println(count);
System.out.println(sb.toString());
}
Консоль только печатает 4
. StringBuilder
Объект все еще имеет значение ""
.
Когда я добавляю операцию фильтра: filter(s -> true)
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
var count = Stream.of(new String[]{"1", "2", "3", "4"})
.filter(s -> true)
.map(sb::append)
.count();
System.out.println(count);
System.out.println(sb.toString());
}
Выход изменяется на:
4
1234
Как эта, казалось бы, избыточная операция фильтра меняет поведение составного потокового конвейера?
java
java-stream
atalantus
источник
источник
Ответы:
Операция
count()
терминала в моей версии JDK завершается выполнением следующего кода:Если
filter()
в конвейере операций есть операция, размер потока, который известен изначально, больше не может быть известен (так как онfilter
может отклонить некоторые элементы потока). Таким образом,if
блок не выполняется, выполняются промежуточные операции и, таким образом, изменяется StringBuilder.С другой стороны, если у вас есть только
map()
в конвейере, количество элементов в потоке гарантированно будет таким же, как исходное количество элементов. Таким образом, блок if выполняется, и размер возвращается напрямую, без оценки промежуточных операций.Обратите внимание, что лямбда, переданная для,
map()
нарушает контракт, определенный в документации: предполагается, что это не мешающая операция без сохранения состояния, но не без сохранения состояния. Таким образом, наличие разных результатов в обоих случаях не может рассматриваться как ошибка.источник
flatMap()
может быть в состоянии изменить количество элементов, было ли это причиной того, почему это было изначально нетерпеливым (теперь ленивым)? Таким образом, альтернативой было бы использоватьforEach()
и считать отдельно, еслиmap()
в его нынешнем виде нарушает договор, я полагаю.4 1234
без использования дополнительного фильтра или создания побочных эффектов в операции map ()?int count = array.length; String result = String.join("", array);
Collectors.joining("")
В jdk-9 это было четко задокументировано в документах java.
API Примечание:
источник
Это не то, для чего .map. Предполагается, что он используется для превращения потока «Нечто» в поток «Нечто Остальное». В этом случае вы используете map для добавления строки во внешний Stringbuilder, после чего у вас есть поток «Stringbuilder», каждый из которых был создан операцией map, добавляющей один номер к исходному Stringbuilder.
Ваш поток на самом деле ничего не делает с отображенными результатами в потоке, поэтому вполне разумно предположить, что потоковый процессор может пропустить этот шаг. Вы рассчитываете на побочные эффекты для выполнения работы, которая нарушает функциональную модель карты. Вы бы лучше обслужили, используя forEach для этого. Делайте счет как отдельный поток целиком или установите счетчик с помощью AtomicInt в forEach.
Фильтр заставляет его запускать содержимое потока, поскольку теперь он должен делать что-то условно значимое с каждым элементом потока.
источник