В чем разница между Collection.stream(). ForEach() и Collection.forEach()?
Я понимаю, что с .stream()
я могу использовать цепные операции, например .filter()
, или использовать параллельный поток. Но в чем разница между ними, если мне нужно выполнить небольшие операции (например, распечатать элементы списка)?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
Ответы
Ответ 1
Для простых случаев, таких как показанные, они в основном одинаковы. Тем не менее, существует ряд тонких различий, которые могут быть значительными.
Одна проблема заключается в заказе. При Stream.forEach
порядок undefined. Это вряд ли произойдет с последовательными потоками, но в рамках спецификации для Stream.forEach
выполнить в некотором произвольном порядке. Это часто происходит в параллельных потоках. Напротив, Iterable.forEach
всегда выполняется в порядке итерации Iterable
, если он указан.
Другая проблема связана с побочными эффектами. Действие, указанное в Stream.forEach
, должно быть без вмешательства. (См. пакет java.util.stream doc.) Iterable.forEach
потенциально имеет меньше ограничений. Для коллекций в java.util
Iterable.forEach
обычно использует эту коллекцию Iterator
, большинство из которых предназначены для fail-fast и который будет бросать ConcurrentModificationException
, если сборка будет структурно изменена во время итерации. Однако модификации, которые не являются структурными , разрешены во время итерации. Например, Документация класса ArrayList говорит: "Просто установка значения элемента не является структурной модификацией". Таким образом, действие для ArrayList.forEach
позволяет без проблем устанавливать значения в базовом ArrayList
.
Совпадающие коллекции еще раз разные. Вместо отказоустойчивости они предназначены для слабо согласованных. Полное определение находится по этой ссылке. Вкратце, однако, рассмотрим ConcurrentLinkedDeque
. Действие, переданное его методу forEach
, разрешено изменять базовый deque, даже структурно, и ConcurrentModificationException
никогда не бросается. Однако изменение, которое происходит, может быть или не быть видимым в этой итерации. (Отсюда и "слабая" последовательность.)
Еще одно отличие видно, если Iterable.forEach
выполняет итерацию по синхронизированной коллекции. В такой коллекции Iterable.forEach
берет блокировку коллекции один раз и удерживает ее во всех вызовах метода действия. Вызов Stream.forEach
использует разделитель сбора данных, который не блокируется и который зависит от преобладающего правила невмешательства. Сбор, поддерживающий поток, может быть изменен во время итерации, и если это так, может возникнуть ConcurrentModificationException
или непоследовательное поведение.
Ответ 2
Этот ответ касается производительности различных реализаций циклов. Это только незначительно относится к циклам, которые называются ОЧЕНЬ ЧАСТО (как миллионы вызовов). В большинстве случаев содержимое цикла будет самым дорогим элементом. В ситуациях, когда вы выполняете циклы действительно часто, это может по-прежнему представлять интерес.
Вам следует повторить эти тесты в целевой системе, поскольку это зависит от конкретной реализации (полный исходный код).
Я запускаю openjdk версии 1.8.0_111 на быстром компьютере с Linux.
Я написал тест, который зацикливается на 10 ^ 6 раз по списку, используя этот код с различными размерами для integers
(10 ^ 0 → 10 ^ 5 записей).
Результаты приведены ниже, самый быстрый способ варьируется в зависимости от количества записей в списке.
Но все же в худших ситуациях повторение 10 ^ 5 записей 10 ^ 6 раз занимало 100 секунд для худшего, поэтому другие соображения важнее практически во всех ситуациях.
public int outside = 0;
private void forCounter(List<Integer> integers) {
for(int ii = 0; ii < integers.size(); ii++) {
Integer next = integers.get(ii);
outside = next*next;
}
}
private void forEach(List<Integer> integers) {
for(Integer next : integers) {
outside = next * next;
}
}
private void iteratorForEach(List<Integer> integers) {
integers.forEach((ii) -> {
outside = ii*ii;
});
}
private void iteratorStream(List<Integer> integers) {
integers.stream().forEach((ii) -> {
outside = ii*ii;
});
}
Вот мое время: миллисекунды/функция/количество записей в списке.
Каждый цикл состоит из 10 ^ 6 циклов.
1 10 100 1000 10000
for with index 39 112 920 8577 89212
iterator.forEach 27 116 959 8832 88958
for:each 53 171 1262 11164 111005
iterable.stream.forEach 255 324 1030 8519 88419
Если вы повторите эксперимент, я разместил полный исходный код. Отредактируйте этот ответ и добавьте свои результаты с пометкой протестированной системы.
Использование MacBook Pro, 2,5 ГГц Intel Core i7, 16 ГБ, macOS 10.12.6:
1 10 100 1000 10000
for with index 49 145 887 7614 81130
iterator.forEach 27 106 1047 8516 88044
for:each 46 143 1182 10548 101925
iterable.stream.forEach 393 397 1108 8908 88361
Ответ 3
Нет разницы между двумя упомянутыми вами, по крайней мере концептуально, Collection.forEach()
является просто сокращением.
Внутренне версия stream()
имеет несколько больше накладных расходов из-за создания объекта, но, глядя на время работы, у нее нет накладных расходов.
Обе реализации заканчиваются итерацией по содержимому collection
один раз, а во время итерации распечатывают элемент.