Rozdiel medzi Collection.stream (). ForEach () a Collection.forEach ()

1. Úvod

Existuje niekoľko možností iterácie kolekcie v Jave. V tomto krátkom tutoriáli sa pozrieme na dva podobne vyzerajúce prístupy - Collection.stream (). ForEach () a Collection.forEach ().

Vo väčšine prípadov obidve prinesú rovnaké výsledky, avšak existujú určité jemné rozdiely, na ktoré sa pozrieme.

2. Prehľad

Najskôr vytvorme zoznam, v ktorom sa bude iterovať:

Zoznam zoznam = Arrays.asList ("A", "B", "C", "D");

Najjednoduchším spôsobom je použitie vylepšenej slučky for-loop:

pre (String s: list) {// urobte niečo so s} 

Ak chceme používať funkčný štýl Java, môžeme použiť aj pre každý(). Môžeme tak urobiť priamo v zbierke:

Spotrebiteľ spotrebiteľ = s -> {System.out :: println}; list.forEach (spotrebiteľ); 

Alebo môžeme zavolať pre každý() v streame zbierky:

list.stream (). forEach (spotrebiteľ); 

Obe verzie budú prechádzať zoznamom a vytlačia všetky prvky:

ABCD ABCD

V tomto jednoduchom prípade nie je rozdiel, ktorý pre každý() používame.

3. Exekučný príkaz

Collection.forEach () používa iterátor kolekcie (ak je zadaný). To znamená, že je definované poradie spracovania položiek. Naopak, poradie spracovania: Collection.stream (). ForEach () je nedefinované.

Vo väčšine prípadov nie je rozdiel, ktorú z týchto dvoch možností si vyberieme.

3.1. Paralelné toky

Paralelné streamy nám umožňujú spustiť stream vo viacerých vláknach a v takýchto situáciách je príkaz na vykonanie nedefinovaný. Java vyžaduje iba dokončenie všetkých vlákien pred každou operáciou terminálu, ako je napr Collectors.toList (), sa volá.

Pozrime sa na príklad, kde najskôr zavoláme pre každý() priamo na zbierke a po druhé, na paralelnom toku:

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

Ak spustíme kód niekoľkokrát, uvidíme to list.forEach () spracuje položky v objednávke, zatiaľ čo list.parallelStream (). forEach () produkuje pri každom behu iný výsledok.

Jedným z možných výstupov je:

ABCD CDBA

Ďalším je:

ABCD DBCA

3.2. Vlastné iterátory

Definujme zoznam s vlastným iterátorom na iteráciu kolekcie v opačnom poradí:

trieda ReverseList rozširuje ArrayList {@Override public Iterator iterator () {int startIndex = this.size () - 1; Zoznam zoznamov = toto; Iterátor it = nový Iterátor () {private int currentIndex = startIndex; @Override public boolean hasNext () {return currentIndex> = 0; } @Override public String next () {String next = list.get (currentIndex); currentIndex--; návrat ďalší; } @Override public void remove () {throw new UnsupportedOperationException (); }}; vráť to; }} 

Keď prechádzame zoznamom, znova s pre každý() priamo v zbierke a potom v streame:

Zoznam myList = nový ReverseList (); myList.addAll (zoznam); myList.forEach (System.out :: print); System.out.print (""); myList.stream (). forEach (System.out :: print); 

Získame rôzne výsledky:

DCBA ABCD 

Dôvod rôznych výsledkov je ten pre každý() použitý priamo v zozname používa vlastný iterátor, zatiaľ čo stream (). forEach () jednoducho vezme prvky jeden po druhom zo zoznamu a ignoruje iterátor.

4. Úprava zbierky

Mnoho zbierok (napr. ArrayList alebo HashSet) by sa nemali pri ich iterácii štrukturálne upravovať. Ak je prvok odstránený alebo pridaný počas iterácie, dostaneme a Súbežná úprava výnimkou.

Ďalej sú kolekcie navrhnuté tak, aby zlyhali rýchlo, čo znamená, že výnimka bude vyvolaná, akonáhle dôjde k zmene.

Podobne dostaneme a Súbežná úprava výnimkou, keď pridávame alebo odoberáme prvok počas vykonávania toku prúdov. Výnimka však bude hodená neskôr.

Ďalší jemný rozdiel medzi týmito dvoma pre každý() metód je to, že Java výslovne umožňuje úpravy prvkov pomocou iterátora. Potoky by naopak nemali byť rušivé.

Pozrime sa na odstránenie a úpravu prvkov podrobnejšie.

4.1. Odstraňuje sa prvok

Definujme operáciu, ktorá odstráni posledný prvok („D“) z nášho zoznamu:

Spotrebiteľ removeElement = s -> {System.out.println (s + "" + list.size ()); if (s! = null && s.equals ("A")) {list.remove ("D"); }};

Keď prechádzame zoznamom, posledný prvok sa odstráni po vytlačení prvého prvku („A“):

list.forEach (removeElement);

Odkedy pre každý() je rýchle zlyhanie, zastavíme iteráciu a pred spracovaním ďalšieho prvku sa zobrazí výnimka:

Výnimka 4 vo vlákne „main“ java.util.ConcurrentModificationException na java.util.ArrayList.forEach (ArrayList.java:1252) na ReverseList.main (ReverseList.java:1)

Pozrime sa, čo sa stane, ak použijeme stream (). forEach () namiesto toho:

list.stream (). forEach (removeElement);

Tu pokračujeme v iterácii celého zoznamu, kým sa nezobrazí výnimka:

A 4 B 3 C 3 null 3 Výnimka vo vlákne „main“ java.util.ConcurrentModificationException na java.util.ArrayList $ ArrayListSpliterator.forEachRemaining (ArrayList.java:1380) na java.util.stream.ReferencePipeline $ Head.forEach (ReferencePipeline) .java: 580) na ReverseList.main (ReverseList.java:1)

Java však nezaručuje, že a ConcurrentModificationException je vôbec hodená. To znamená, že by sme nikdy nemali písať program, ktorý závisí od tejto výnimky.

4.2. Meniace sa prvky

Prvok môžeme zmeniť pri iterácii zoznamu:

list.forEach (e -> {list.set (3, "E");});

Aj keď s tým nie je problém urobiť ani jeden Collection.forEach () alebo stream (). forEach ()„Java vyžaduje, aby prevádzka na streame nezasahovala. To znamená, že prvky by sa nemali upravovať počas vykonávania toku prúdu.

Dôvod je ten, že prúd by mal uľahčovať paralelné vykonávanie. Tu by modifikácia prvkov streamu mohla viesť k neočakávanému správaniu.

5. Záver

V tomto článku sme videli niekoľko príkladov, ktoré ukazujú jemné rozdiely medzi nimi Collection.forEach () a Collection.stream (). ForEach ().

Je však dôležité poznamenať, že všetky vyššie uvedené príklady sú triviálne a sú určené iba na porovnanie dvoch spôsobov opakovania zbierky. Nemali by sme písať kód, ktorého správnosť sa spolieha na zobrazené správanie.

Ak nepotrebujeme stream, ale chceme iba iterovať cez kolekciu, prvou voľbou by malo byť použitie pre každý() priamo na zbierke.

Zdrojový kód príkladov v tomto článku je k dispozícii na serveri GitHub.


$config[zx-auto] not found$config[zx-overlay] not found