В чем разница между 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 один раз, а во время итерации распечатывают элемент.