Razlika između Collection.stream (). ForEach () i Collection.forEach ()

1. Uvod

Postoji nekoliko mogućnosti za itiranje preko zbirke na Javi. U ovom ćemo kratkom vodiču pogledati dva pristupa sličnog izgleda - Collection.stream (). ForEach () i Collection.forEach ().

U većini slučajeva oba će dati iste rezultate, međutim, postoje neke suptilne razlike koje ćemo pogledati.

2. Pregled

Prvo, kreirajmo popis za ponavljanje:

Popis popisa = Arrays.asList ("A", "B", "C", "D");

Najjednostavniji način je korištenje poboljšane for-loop:

for (String s: list) {// učiniti nešto sa s} 

Ako želimo koristiti Java u funkcionalnom stilu, možemo koristiti i za svakoga(). To možemo učiniti izravno na kolekciji:

Potrošački potrošač = s -> {System.out :: println}; list.forEach (potrošač); 

Ili, možemo nazvati za svakoga() u toku kolekcije:

list.stream (). forEach (potrošač); 

Obje verzije će se prelistati po popisu i ispisati sve elemente:

ABCD ABCD

U ovom jednostavnom slučaju nema razlike koja za svakoga() koristimo.

3. Nalog za izvršenje

Collection.forEach () koristi iterator zbirke (ako je jedan naveden). To znači da je definiran redoslijed obrade predmeta. Suprotno tome, redoslijed obrade Collection.stream (). ForEach () je nedefinirano.

U većini slučajeva nije bitno koju ćemo od dvije odabrati.

3.1. Paralelni tokovi

Paralelni tokovi omogućuju nam izvršavanje toka u više niti, a u takvim situacijama redoslijed izvršenja nije definiran. Java zahtijeva samo da se sve niti završe prije bilo koje operacije terminala, kao što je Collectors.toList (), Zove se.

Pogledajmo primjer gdje prvo zovemo za svakoga() izravno na kolekciji, i drugo, na paralelnom toku:

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

Ako pokrenimo kod nekoliko puta, to vidimo list.forEach () obrađuje stavke po redoslijedu umetanja, dok list.parallelStream (). forEach () daje različit rezultat pri svakom izvođenju.

Jedan od mogućih rezultata je:

ABCD CDBA

Još jedan je:

ABCD DBCA

3.2. Prilagođeni iteratori

Definirajmo popis s prilagođenim iteratorom koji će prelaziti preko zbirke obrnutim redoslijedom:

klasa ReverseList proširuje ArrayList {@Override javni iterator iterator () {int startIndex = this.size () - 1; Lista popisa = ovo; Iterator it = novi Iterator () {private int currentIndex = startIndex; @Override public boolean hasNext () {return currentIndex> = 0; } @Override public String next () {String next = list.get (currentIndex); currentIndex--; povratak sljedeći; } @Override public void remove () {throw new UnsupportedOperationException (); }}; vrati to; }} 

Kad prelistamo popis, opet s za svakoga() izravno na kolekciji, a zatim na streamu:

Popis myList = novi ReverseList (); myList.addAll (popis); myList.forEach (System.out :: print); System.out.print (""); myList.stream (). forEach (System.out :: print); 

Dobivamo različite rezultate:

DCBA ABCD 

Razlog različitih rezultata je taj za svakoga() koristi se izravno na popisu koristi prilagođeni iterator, dok stream (). forEach () jednostavno uzima elemente jedan po jedan s popisa, zanemarujući iterator.

4. Izmjena Zbirke

Mnoge zbirke (npr. ArrayList ili HashSet) ne bi trebali biti strukturno modificirani dok se prevlače po njima. Ako se element ukloni ili doda tijekom iteracije, dobit ćemo Istodobna izmjena iznimka.

Nadalje, zbirke su dizajnirane za brzi pad, što znači da se iznimka baca čim dođe do modifikacije.

Slično ćemo dobiti i Istodobna izmjena iznimka kada dodamo ili uklonimo element tijekom izvođenja protočnog cjevovoda. Međutim, iznimka će se izbaciti kasnije.

Još jedna suptilna razlika između njih dvoje za svakoga() metode je da Java izričito dopušta izmjenu elemenata pomoću iteratora. Za razliku od njih, potoci ne bi trebali ometati rad.

Pogledajmo uklanjanje i modificiranje elemenata detaljnije.

4.1. Uklanjanje elementa

Definirajmo operaciju kojom se uklanja posljednji element ("D") s našeg popisa:

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

Kad prelazimo preko popisa, posljednji se element uklanja nakon ispisa prvog elementa ("A"):

list.forEach (removeElement);

Od za svakoga() je neuspješan, zaustavljamo ponavljanje i vidimo iznimku prije obrade sljedećeg elementa:

Iznimka 4 u niti "main" java.util.ConcurrentModificationException na java.util.ArrayList.forEach (ArrayList.java:1252) na ReverseList.main (ReverseList.java:1)

Pogledajmo što će se dogoditi ako koristimo stream (). forEach () umjesto toga:

list.stream (). forEach (removeElement);

Ovdje nastavljamo s ponavljanjem cijelog popisa prije nego što vidimo iznimku:

A 4 B 3 C 3 null 3 Iznimka u niti "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)

Međutim, Java ne jamči da a ConcurrentModificationException se uopće baca. To znači da nikada ne bismo trebali pisati program koji ovisi o ovoj iznimci.

4.2. Mijenjanje elemenata

Element možemo promijeniti tijekom iteriranja po popisu:

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

Međutim, iako nema problema da se to učini pomoću bilo kojeg Collection.forEach () ili stream (). forEach (), Java zahtijeva da rad na toku ne ometa. To znači da se elementi ne bi trebali mijenjati tijekom izvođenja tekućeg cjevovoda.

Razlog tome je što bi tok trebao olakšati paralelno izvršavanje. Ovdje izmjena elemenata toka može dovesti do neočekivanog ponašanja.

5. Zaključak

U ovom smo članku vidjeli nekoliko primjera koji pokazuju suptilne razlike između Collection.forEach () i Collection.stream (). ForEach ().

Međutim, važno je napomenuti da su svi gore prikazani primjeri trivijalni i da su samo namijenjeni usporedbi dva načina ponavljanja kroz zbirku. Ne bismo trebali pisati kod čija se ispravnost oslanja na prikazano ponašanje.

Ako ne trebamo stream, već samo želimo itiriranje preko zbirke, prvi izbor trebao bi biti upotreba za svakoga() izravno na kolekciji.

Izvorni kod za primjere u ovom članku dostupan je na GitHubu.