W przypadku prostych przypadków, takich jak pokazany, są one w większości takie same. Istnieje jednak wiele subtelnych różnic, które mogą być znaczące.
Jeden problem dotyczy zamawiania. Z Stream.forEach
, kolejność jest niezdefiniowana . Jest mało prawdopodobne, aby wystąpiło to z sekwencyjnymi strumieniami, ale jest to zgodne ze specyfikacją Stream.forEach
do wykonywania w dowolnej kolejności. Zdarza się to często w równoległych strumieniach. Natomiast Iterable.forEach
jest zawsze wykonywane w kolejności iteracji Iterable
, jeśli jest określony.
Innym problemem są skutki uboczne. Działanie określone w Stream.forEach
musi być niezakłócające . (Zobacz dokumentację pakietu java.util.stream .) Iterable.forEach
Potencjalnie ma mniej ograniczeń. W przypadku kolekcji w java.util
, na Iterable.forEach
ogół używa tej kolekcji Iterator
, z których większość jest zaprojektowana tak, aby działała szybko i niezawodnie, i która wyrzuci, ConcurrentModificationException
jeśli kolekcja zostanie zmodyfikowana strukturalnie podczas iteracji. Jednak modyfikacje, które nie są strukturalne, są dozwolone podczas iteracji. Na przykład dokumentacja klasy ArrayList mówi „zwykłe ustawienie wartości elementu nie jest modyfikacją strukturalną”. Tak więc akcja dlaArrayList.forEach
wolno ArrayList
bez problemu ustawiać wartości w instrumencie bazowym .
Współbieżne kolekcje są jeszcze inne. Zamiast szybko działać, są zaprojektowane tak, aby były słabo spójne . Pełna definicja znajduje się pod tym linkiem. Zastanów się jednak krótko ConcurrentLinkedDeque
. Przekazane jej działanie forEach
metody jest możliwość modyfikowania podstawowej deque, nawet strukturalnie, a ConcurrentModificationException
nigdy nie jest wyrzucane. Jednak wprowadzona modyfikacja może, ale nie musi być widoczna w tej iteracji. (Stąd „słaba” konsystencja).
Jeszcze inna różnica jest widoczna, jeśli Iterable.forEach
iteruje zsynchronizowaną kolekcję. W takiej kolekcji Iterable.forEach
wystarczy raz zablokować kolekcję i przytrzymać ją we wszystkich wywołaniach metody akcji. W Stream.forEach
wywołaniu zastosowano spliterator kolekcji, który nie blokuje się i który opiera się na dominującej zasadzie nieingerencji. Kolekcja wspierająca strumień może być modyfikowana podczas iteracji, a jeśli tak, ConcurrentModificationException
może to spowodować niespójne zachowanie.
Iterable.forEach takes the collection's lock
. Skąd ta informacja? Nie mogę znaleźć takiego zachowania w źródłach JDK.ArrayList
dość rygorystyczne sprawdzanie pod kątem równoczesnej modyfikacji, i dlatego często będą rzucaćConcurrentModificationException
. Ale nie jest to gwarantowane, szczególnie w przypadku strumieni równoległych. Zamiast CME możesz otrzymać nieoczekiwaną odpowiedź. Rozważ także modyfikacje niestrukturalne źródła strumienia. W przypadku strumieni równoległych nie wiadomo, który wątek przetworzy dany element, ani czy został on przetworzony w momencie jego modyfikacji. To ustanawia warunki wyścigu, w których możesz uzyskać różne wyniki przy każdym biegu i nigdy nie uzyskać CME.Ta odpowiedź dotyczy samego wykonania różnych implementacji pętli. Jest to tylko nieznacznie istotne dla pętli, które są nazywane BARDZO CZĘSTO (jak miliony połączeń). W większości przypadków zawartość pętli będzie zdecydowanie najdroższym elementem. W sytuacjach, w których często zapętlasz się, może to nadal być interesujące.
Powtórz te testy w systemie docelowym, ponieważ jest to specyficzne dla implementacji ( pełny kod źródłowy ).
Używam openjdk w wersji 1.8.0_111 na szybkim komputerze z systemem Linux.
Napisałem test, który zapętla 10 ^ 6 razy nad Listą, używając tego kodu o różnych rozmiarach dla
integers
(10 ^ 0 -> 10 ^ 5 wpisów).Wyniki są poniżej, najszybsza metoda różni się w zależności od liczby wpisów na liście.
Ale wciąż w najgorszych sytuacjach zapętlenie ponad 10 ^ 5 wpisów 10 ^ 6 razy zajęło 100 sekund dla najgorzej wykonującego, więc inne względy są ważniejsze w praktycznie wszystkich sytuacjach.
Oto moje czasy: milisekundy / funkcja / liczba pozycji na liście. Każdy bieg to 10 ^ 6 pętli.
Jeśli powtórzysz eksperyment, opublikowałem pełny kod źródłowy . Przeprowadź edycję tej odpowiedzi i dodaj wyniki z notacją testowanego systemu.
Używając MacBooka Pro, 2,5 GHz Intel Core i7, 16 GB, macOS 10.12.6:
Java 8 Hotspot VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
Java 11 Hotspot VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
(ta sama maszyna jak powyżej, inna wersja JDK)
Java 11 OpenJ9 VM - 3,4 GHz Intel Xeon, 8 GB, Windows 10 Pro
(ta sama maszyna i wersja JDK jak wyżej, inna VM)
VM 8 Hotspot VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
Java 11 Hotspot VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
(ta sama maszyna jak powyżej, inna wersja JDK)
Java 11 OpenJ9 VM - 2,8 GHz AMD, 64 GB, Windows Server 2016
(ta sama maszyna i wersja JDK jak wyżej, inna VM)
Wybrana implementacja maszyny wirtualnej ma również znaczenie Hotspot / OpenJ9 / etc.
źródło
Nie ma różnicy między tymi dwoma, o których wspomniałeś, przynajmniej koncepcyjnie,
Collection.forEach()
jest to tylko skrót.Wewnętrznie
stream()
wersja ma nieco więcej narzutu z powodu tworzenia obiektów, ale patrząc na czas działania, nie ma tam też narzutu.Obie implementacje kończą się
collection
raz iteracją zawartości, a podczas iteracji wypisują element.źródło
Stream
tworzonego obiektu czy poszczególnych obiektów? AFAIK, aStream
nie powiela elementów.Collection.forEach () używa iteratora kolekcji (jeśli jest określony). Oznacza to, że kolejność przetwarzania pozycji jest zdefiniowana. Natomiast kolejność przetwarzania Collection.stream (). ForEach () jest niezdefiniowana.
W większości przypadków nie ma znaczenia, który z dwóch wybieramy. Równoległe strumienie pozwalają nam na wykonanie strumienia w wielu wątkach, aw takich sytuacjach kolejność wykonywania jest niezdefiniowana. Java wymaga jedynie zakończenia wszystkich wątków przed wywołaniem jakiejkolwiek operacji terminalowej, takiej jak Collectors.toList (). Spójrzmy na przykład, w którym najpierw wywołujemy forEach () bezpośrednio w kolekcji, a po drugie w równoległym strumieniu:
Jeśli uruchomimy kod kilka razy, zobaczymy, że list.forEach () przetwarza elementy w kolejności wstawiania, podczas gdy list.parallelStream (). ForEach () generuje inny wynik przy każdym uruchomieniu. Jednym z możliwych wyników jest:
Kolejny to:
źródło