it-swarm.com.ru

В чем разница между Collection.stream (). ForEach () и Collection.forEach ()?

Я понимаю, что с .stream() я могу использовать цепные операции, такие как .filter(), или использовать параллельный поток. Но в чем разница между ними, если мне нужно выполнить небольшие операции (например, печать элементов списка)?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
246
VladS

Для простых случаев, таких как иллюстрированный, они в основном одинаковы. Тем не менее, есть ряд тонких различий, которые могут быть значительными.

Одна проблема с заказом. С Stream.forEach порядок не определен . Это вряд ли произойдет с последовательными потоками, тем не менее, это в пределах спецификации для Stream.forEach для выполнения в произвольном порядке. Это часто происходит в параллельных потоках. Напротив, Iterable.forEach всегда выполняется в порядке итерации Iterable, если он указан.

Другая проблема связана с побочными эффектами. Действие, указанное в Stream.forEach, должно быть не мешающим . (См. Документация пакета Java.util.stream .) Iterable.forEach потенциально имеет меньше ограничений. Для коллекций в Java.util обычно Iterable.forEach будет использовать Iterator этой коллекции, большинство из которых предназначены для fail-fast и которые будут выбрасывать ConcurrentModificationException, если коллекция структурно изменена во время итерации. Однако модификации, которые не являются структурными , разрешены во время итерации. Например, документация по классу ArrayList говорит, что "просто установка значения элемента не является структурной модификацией". Таким образом, действие для ArrayList.forEach позволяет без проблем установить значения в базовом ArrayList.

Параллельные коллекции еще раз отличаются. Вместо быстрого отказа они разработаны так, чтобы быть слабо согласованными . Полное определение по этой ссылке. Вкратце рассмотрим ConcurrentLinkedDeque. Действие, переданное его методу forEach, позволяет изменять базовую деку, даже структурно, и ConcurrentModificationException никогда не генерируется. Тем не менее, изменение, которое происходит, может быть или не быть видимым в этой итерации. (Отсюда и "слабая" последовательность.)

Еще одно отличие заметно, если Iterable.forEach выполняет итерацию по синхронизированной коллекции. В такой коллекции Iterable.forEachберет блокировку коллекции один раз и удерживает ее во всех вызовах метода действия. Вызов Stream.forEach использует сплитератор коллекции, который не блокируется и который опирается на правило невмешательства. Коллекция, поддерживающая поток, может быть изменена во время итерации, и, если это так, может возникнуть ConcurrentModificationException или противоречивое поведение.

244
Stuart Marks

Этот ответ касается производительности различных реализаций циклов. Это только незначительно относится к циклам, которые называются ОЧЕНЬ ЧАСТО (как миллионы вызовов). В большинстве случаев содержимое цикла будет самым дорогим элементом. Для ситуаций, когда вы делаете петли действительно часто, это все еще может представлять интерес.

Вы должны повторить эти тесты в целевой системе, так как это зависит от реализации ( полный исходный код ).

Я запускаю 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

Если вы повторите эксперимент, я разместил полный исходный код . Пожалуйста, отредактируйте этот ответ и добавьте результаты с пометкой протестированной системы.

I got:
                                  1    10    100    1000    10000
              iterator.forEach   27   106   1047    8516    88044
                      for:each   46   143   1182   10548   101925
       iterable.stream.forEach  393   397   1108    8908    88361
                for with index   49   145    887    7614    81130

(MacBook Pro, 2,5 ГГц Intel Core i7, 16 ГБ, macOS 10.12.6)

16
Angelo Fuchs

Между этими двумя понятиями нет никакой разницы, по крайней мере, концептуально Collection.forEach() - это просто сокращение.

Внутренне версия stream() имеет несколько больше накладных расходов из-за создания объекта, но, смотря на время выполнения, она тоже не имеет никаких накладных расходов.

Обе реализации заканчиваются итерацией по содержимому collection один раз, и во время итерация выводит элемент.

8
skiwi