Stream.forEachOrdered: примеры (JAVA)
Stream.forEachOrdered(Consumer super T> action): voidОбщее описание и сигнатура
Метод Stream.forEachOrdered является терминальной операцией Java Stream API, доступной начиная с Java 8. Назначение метода - применять заданный Consumer<? super T> ко всем элементам потока с соблюдением порядка обхода (encounter order) тогда, когда он определён. Метод не возвращает значение (тип void).
Сигнатура для объектного потока:
void forEachOrdered(Consumer<? super T> action)
Для примитивных потоков имеются аналогичные перегрузки:
void forEachOrdered(IntConsumer action) void forEachOrdered(LongConsumer action) void forEachOrdered(DoubleConsumer action)
Поведение и использование:
- Если поток является упорядоченным (например, List.stream()), действие будет применяться в порядке элементов источника.
- Если поток параллельный,
forEachOrderedгарантирует применение в порядке обхода, но это может снизить параллельную производительность из-за синхронизации вывода порядка. - Если поток помечен как unordered (например, вызовом
unordered()), порядок не гарантируется, и реализация может применять действие в любом порядке. - Метод является терминальной операцией: после его вызова поток считается использованным и повторный доступ к нему приведет к исключению.
Исключения и пограничные случаи:
- Если передать
nullв качествеaction, будет брошеноNullPointerException. - Если действие выбрасывает unchecked-исключение, оно пробрасывается вызывающему. Если поток параллельный, может наблюдаться агрегация исключений в зависимости от реализации.
- Метод не возвращает результат для дальнейшей композиции; для возвращаемых результатов следует использовать collect или reduce.
Короткие примеры использования
Пример 1: последовательный поток, простой вывод в порядке элементов.
List<String> list = List.of("a", "b", "c");
list.stream().forEachOrdered(System.out::print);
abc
Пример 2: параллельный поток - сохранение исходного порядка при выводе.
List<Integer> nums = List.of(1,2,3,4);
nums.parallelStream().forEachOrdered(n -> System.out.print(n + " "));
1 2 3 4
Пример 3: поток без порядка - порядок не гарантируется.
List<Integer> nums = List.of(1,2,3,4);
nums.stream().unordered().parallel().forEachOrdered(n -> System.out.print(n + " "));
(порядок может быть отличным от 1 2 3 4, например 3 1 4 2)
Пример 4: примитивный поток (IntStream).
IntStream.range(1, 5).forEachOrdered(System.out::print);
1234
Похожие методы в Java и отличия
Краткий обзор альтернатив и выбор в зависимости от задачи:
forEach: выполняет действие для каждого элемента, но не гарантирует порядок на параллельных потоках. Предпочтителен при необходимости максимальной скорости и когда порядок не важен.collect: собирает элементы в коллекцию, затем можно обработать результат в нужном порядке. Уместен при необходимости агрегировать или возвращать значение.reduce: применяется для свертки элементов в одно значение без побочных эффектов. Подходит для функциональных вычислений без мутаций.Iterator.forEachRemaining: позволяет итеративно обработать элементы с сохранением порядка, при этом контроль над итерацией у вызывающего.peek: промежуточная операция для отладки, не следует полагаться на её вызовы как на гарантированный итоговый обход.
При выборе между forEach и forEachOrdered следует соотнести требование к порядку и требования к производительности. Для сохранения порядка в параллельной обработке выбирается forEachOrdered, при допустимости любого порядка - forEach или параллельные collect-операции.
Эквиваленты в других языках и отличия
Короткие сравнения с примерами и результатами.
JavaScript (Array)
[1,2,3].forEach(x => console.log(x));
1 2 3
В JS порядок выполнения синхронного forEach всегда сохраняется. В отличие от Java, нет встроенной параллельной версии коллекций.
Python
for x in [1,2,3]:
print(x)
1 2 3
В Python порядок последовательный. Для параллельной обработки используются модули concurrent.futures, map в них не гарантирует порядок без дополнительных мер.
PHP
array_walk([1,2,3], function($v){ echo $v.PHP_EOL; });
1 2 3
Поведение последовательное; параллелизм редко встроен в стандартные массивные операции.
C#
new[]{1,2,3}.AsParallel().ForAll(Console.WriteLine);
(порядок может отличаться, например 2 1 3)
В C# PLINQ метод ForAll выполняет действие параллельно без сохранения порядка; для сохранения порядка можно использовать AsOrdered().
Go
for _, v := range []int{1,2,3} {
fmt.Println(v)
}
1 2 3
Go выполняет цикл в порядке, но параллелизм достигается явно через горутины, и тогда порядок теряется, если не синхронизировать.
Kotlin
listOf(1,2,3).forEach { println(it) }
1 2 3
Kotlin использует похожую модель на Java; при Sequence и Stream-подобных операциях порядок сохраняется, есть средства для параллельной обработки через корутины или Java-параллелизм.
Основное отличие Java forEachOrdered от перечисленных методов в других языках - встроённая гарантия порядка при параллельной обработке потоков. Во многих языках поддержка параллельного последовательного обхода требует явной синхронизации.
Типичные ошибки и примеры
Пошаговые типичные проблемы при использовании forEachOrdered.
Передача null
List<String> list = List.of("a");
list.stream().forEachOrdered(null);
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:...) ...
Повторное использование потока
Stream<Integer> s = Stream.of(1,2);
s.forEachOrdered(System.out::print);
s.forEachOrdered(System.out::print);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
Побочные эффекты с параллельностью
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
IntStream.range(0,1000).parallel().forEachOrdered(i -> list.add(i));
System.out.println(list.size());
1000
Если убрать synchronizedList и использовать обычный ArrayList, возможны непредсказуемые результаты или ConcurrentModificationException. Главная ошибка - применение небезопасных для потоков структур без защиты.
Ожидание возвращаемого значения
int sum = Stream.of(1,2,3).forEachOrdered(System.out::print);
(код не компилируется - forEachOrdered возвращает void)
При попытке получить результат следует использовать collect или reduce.
История и изменения
Метод появился в Java 8 вместе с Stream API и с тех пор остался в той же форме. Для объектных и примитивных потоков существуют соответствующие перегрузки. В более поздних релизах Java происходили общие улучшения производительности и оптимизации потоков, но семантика forEachOrdered не менялась.
Стоит учитывать, что оптимизации могут зависеть от характеристик Spliterator источника (например, CONCURRENT, ORDERED, SIZED). Для специфичных источников поведения производительности могут меняться в разных версиях JVM, но контракт метода остался прежним.
Расширенные и неочевидные сценарии
1. Параллельный поток с дорогой операцией и сохранением порядка
List<Integer> ids = IntStream.range(1,6).boxed().collect(Collectors.toList());
ids.parallelStream().forEachOrdered(id -> {
try { Thread.sleep(100 * (6 - id)); } catch (InterruptedException ignored) {}
System.out.print(id + " ");
});
1 2 3 4 5
Даже при параллельной обработке вывод остаётся упорядоченным. Производительность может быть хуже, чем при использовании forEach, так как результат собирается в порядке.
2. Использование с бесконечным потоком и limit
Stream.iterate(1, n -> n + 1)
.parallel()
.limit(5)
.forEachOrdered(System.out::println);
1 2 3 4 5
Комбинация limit и forEachOrdered может потребовать от реализации синхронизации для определения первых N элементов.
3. Собирающий накопитель через побочный эффект с сохранением порядка
List<String> out = Collections.synchronizedList(new ArrayList<>());
Stream.of("a","b","c").parallel().forEachOrdered(out::add);
System.out.println(out);
[a, b, c]
Без synchronizedList порядок может сохраниться, но безопасность потоков не гарантируется. Для безопасного и детерминированного сбора предпочтительнее collect с соответствующим Collector.
4. Логирование с гарантией упорядоченности в конкурентной среде
ExecutorService ex = Executors.newFixedThreadPool(4);
List<Integer> data = IntStream.range(1,9).boxed().collect(Collectors.toList());
try {
data.parallelStream().forEachOrdered(i -> {
// имитация задачи, результаты логируются в порядке источника
try { Thread.sleep(50 * (i % 3)); } catch (InterruptedException ignored) {}
System.out.println(Thread.currentThread().getName() + ":" + i);
});
} finally {
ex.shutdown();
}
(строки будут выводиться в порядке 1..8, потоки могут отличаться) pool-1-thread-1:1 pool-1-thread-3:2 ... pool-1-thread-2:8
Полезно при формировании логов, где важна последовательность записей, но обработка может быть распараллелена.
5. Обработка исключений внутри действия
Stream.of(1,0,2).forEachOrdered(i -> {
try {
System.out.println(10 / i);
} catch (ArithmeticException e) {
System.out.println("div0 at " + i);
}
});
10 div0 at 0 5
Исключения внутри consumer можно обрабатывать локально, иначе они прерывают выполнение и пробрасываются вверх.
джава Stream.forEachOrdered function comments
- джава Stream.forEachOrdered - аргументы и возвращаемое значение
- Функция java Stream.forEachOrdered - описание
- Stream.forEachOrdered - примеры
- Stream.forEachOrdered - похожие методы на java
- Stream.forEachOrdered на javascript, c#, python, php
- Stream.forEachOrdered изменения java
- Примеры Stream.forEachOrdered на джава