Яка різниця між Collection.stream (). ForEach () та Collection.forEach ()?


286

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

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

Відповіді:


287

Для простих випадків, таких як ілюстрований, вони здебільшого однакові. Однак існує ряд тонких відмінностей, які можуть бути істотними.

Одне питання - із замовленням. З Stream.forEach, порядок не визначено . Це навряд чи трапляється з послідовними потоками, все-таки це в межах специфікації для Stream.forEachвиконання в якомусь довільному порядку. Це трапляється часто в паралельних потоках. Навпаки, Iterable.forEachзавжди виконується в порядку ітерації Iterable, якщо він вказаний.

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

Супутні колекції ще раз відрізняються. Замість того, щоб вийти з ладу, вони розроблені таким чином, щоб вони були слабко послідовними . Повне визначення знаходиться за цим посиланням. Однак коротко, подумайте ConcurrentLinkedDeque. Дія, передана його forEachметоду , дозволяє навіть структурно модифікувати основу дека, і ConcurrentModificationExceptionніколи не кидається. Однак модифікація, що виникає, може бути або не помітна в цій ітерації. (Звідси "слабка" послідовність.)

Ще одна різниця помітна, якщо Iterable.forEachвона повторюється над синхронізованою колекцією. У такій колекції Iterable.forEach знімає замок колекції один раз і тримає його в усіх викликах методу дії. У Stream.forEachвиклику використовується сплітератор колекції, який не блокується, і який спирається на діюче правило невтручання. Підтримка потоку колекції може бути змінена під час ітерації, і якщо вона є, це ConcurrentModificationExceptionможе призвести до непослідовної поведінки.


Iterable.forEach takes the collection's lock. Звідки ця інформація? Я не можу знайти таку поведінку в джерелах JDK.
турбанов


@Stuart, чи можете ви детальніше зупинитися на невтручанні. Stream.forEach () також викине ConcurrentModificationException (принаймні для мене).
yuranos

1
@ yuranos87 Багато колекцій, як-от ArrayListдосить чітко перевіряють наявність одночасних модифікацій, і тому часто викидають ConcurrentModificationException. Але це не гарантується, особливо для паралельних потоків. Замість CME ви можете отримати несподівану відповідь. Розглянемо також неструктурні модифікації джерела потоку. Для паралельних потоків ви не знаєте, який потік буде обробляти певний елемент, а також чи оброблявся він під час модифікації. Це налаштовує умови гонки, де ви можете отримувати різні результати на кожному пробігу і ніколи не отримувати CME.
Стюарт Маркс

30

Ця відповідь стосується виконання різних циклів реалізації. Його єдине незначне значення для циклів, які називаються ДУЖЕ ЗАРАЗ (наприклад, мільйони дзвінків). У більшості випадків вміст циклу буде, безумовно, найдорожчим елементом. У ситуаціях, коли ви циклуєте дуже часто, це все ще може зацікавити.

Ви повинні повторити ці тести в цільовій системі, оскільки це специфічне для впровадження, ( повний вихідний код ).

Я запускаю 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
       iterator.forEach   27   116    959    8832    88958
               for:each   53   171   1262   11164   111005
         for with index   39   112    920    8577    89212
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
       iterator.forEach   27   106   1047    8516    88044
               for:each   46   143   1182   10548   101925
         for with index   49   145    887    7614    81130
iterable.stream.forEach  393   397   1108    8908    88361

Java 8 Hotspot VM - 3,4 ГГц Intel Xeon, 8 ГБ, Windows 10 Pro

                            1    10    100    1000    10000
        iterator.forEach   30   115    928    8384    85911
                for:each   40   125   1166   10804   108006
          for with index   30   120    956    8247    81116
 iterable.stream.forEach  260   237   1020    8401    84883

Java 11 Hotspot VM - 3,4 ГГц Intel Xeon, 8 ГБ, Windows 10 Pro
(та ж машина, що і вище, інша версія JDK)

                            1    10    100    1000    10000
        iterator.forEach   20   104    940    8350    88918
                for:each   50   140    991    8497    89873
          for with index   37   140    945    8646    90402
 iterable.stream.forEach  200   270   1054    8558    87449

Java 11 OpenJ9 VM - 3,4 ГГц Intel Xeon, 8 ГБ, Windows 10 Pro
(та ж машина та версія JDK, як і вище, різні VM)

                            1    10    100    1000    10000
        iterator.forEach  211   475   3499   33631   336108
                for:each  200   375   2793   27249   272590
          for with index  384   467   2718   26036   261408
 iterable.stream.forEach  515   714   3096   26320   262786

VM Java 8 Hotspot - AMD 2,8 ГГц, 64 ГБ, Windows Server 2016

                            1    10    100    1000    10000
        iterator.forEach   95   192   2076   19269   198519
                for:each  157   224   2492   25466   248494
          for with index  140   368   2084   22294   207092
 iterable.stream.forEach  946   687   2206   21697   238457

Java 11 Hotspot VM - 2,8 ГГц AMD, 64 ГБ, Windows Server 2016
(та сама машина, що і вище, інша версія JDK)

                            1    10    100    1000    10000
        iterator.forEach   72   269   1972   23157   229445
                for:each  192   376   2114   24389   233544
          for with index  165   424   2123   20853   220356
 iterable.stream.forEach  921   660   2194   23840   204817

Java 11 OpenJ9 VM - AMD 2,8 ГГц, 64 ГБ, Windows Server 2016
(та ж машина та версія JDK, як вище, різні VM)

                            1    10    100    1000    10000
        iterator.forEach  592   914   7232   59062   529497
                for:each  477  1576  14706  129724  1190001
          for with index  893   838   7265   74045   842927
 iterable.stream.forEach 1359  1782  11869  104427   958584

Реалізація VM, яку ви обрали, також змінює Hotspot / OpenJ9 / тощо.


3
Це дуже приємна відповідь, дякую! Але з першого погляду (і також з другого) незрозуміло, який метод відповідає якому експерименту.
torina

Я відчуваю, що ця відповідь потребує більше голосів для тестування коду :).
Кори

для прикладів тестів +1
Centos

8

Немає різниці між цими двома вами згаданими, принаймні концептуально, Collection.forEach()це лише скорочення.

Внутрішня stream()версія має дещо більше накладних витрат через створення об'єктів, але дивлячись на час роботи, вона також не має накладних витрат.

Обидві реалізації закінчуються ітерацією над collectionвмістом один раз, і під час ітерації роздруковується елемент.


Ви називаєте створення об'єктів, які ви згадуєте, чи ви маєте на увазі Streamстворене або окремих об'єктів? AFAIK, a Streamне дублює елементи.
Раффі Хатчадуріан

30
Ця відповідь, здається, суперечить чудовій відповіді, яку написав джентльмен, який розробляє основні бібліотеки Java в корпорації Oracle.
Dawood ibn Kareem

0

Collection.forEach () використовує ітератор колекції (якщо такий вказано). Це означає, що визначено порядок обробки елементів. На відміну від цього, порядок обробки Collection.stream (). ForEach () не визначений.

У більшості випадків це не має значення, яку з двох ми обираємо. Паралельні потоки дозволяють виконувати потік у декількох потоках, і в таких ситуаціях порядок виконання не визначений. Java вимагає лише всіх потоків до завершення роботи терміналу, наприклад Collectors.toList (). Давайте розглянемо приклад, коли ми спочатку викликаємо forEach () безпосередньо в колекції, а по-друге, на паралельному потоці:

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);

Якщо ми запускаємо код кілька разів, ми бачимо, що list.forEach () обробляє елементи в порядку вставки, тоді як list.parallelStream (). ForEach () дає різний результат при кожному запуску. Один з можливих результатів:

ABCD CDBA

Ще один:

ABCD DBCA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.