Különbség a Collection.stream (). ForEach () és a Collection.forEach () között

1. Bemutatkozás

Számos lehetőség van a Java-gyűjtemények iterálására. Ebben a rövid bemutatóban két hasonló megjelenésű megközelítést vizsgálunk meg - Collection.stream (). ForEach () és Collection.forEach ().

A legtöbb esetben mindkettő ugyanazt az eredményt hozza, azonban vannak finom különbségek, amelyeket megvizsgálunk.

2. Áttekintés

Először hozzunk létre egy listát az ismétléshez:

Lista lista = Arrays.asList ("A", "B", "C", "D");

A legegyszerűbb módszer a továbbfejlesztett for-loop használata:

for (String s: list) {// tegyen valamit s-vel} 

Ha funkcionális stílusú Java-t akarunk használni, akkor a az egyes(). Megtehetjük közvetlenül a gyűjteményen:

Fogyasztói fogyasztó = s -> {System.out :: println}; list.forMinden (fogyasztó); 

Vagy hívhatunk az egyes() a gyűjtemény adatfolyamán:

list.stream (). forEach (fogyasztó); 

Mindkét verzió ismétli a listát, és kinyomtatja az összes elemet:

ABCD ABCD

Ebben az egyszerű esetben nincs különbség, hogy melyik az egyes() használunk.

3. Végrehajtási parancs

Collection.forEach () a gyűjtemény iterátorát használja (ha van ilyen megadva). Ez azt jelenti, hogy az elemek feldolgozási sorrendje meg van határozva. Ezzel szemben a Collection.stream (). ForEach () nincs meghatározva.

A legtöbb esetben nincs különbség a kettő közül melyiket választjuk.

3.1. Párhuzamos folyamok

A párhuzamos folyamok lehetővé teszik számunkra, hogy az adatfolyamot több szálon hajtsuk végre, és ilyen helyzetekben a végrehajtási sorrend nincs meghatározva. A Java csak az összes szál befejezését igényli a terminál műveletek előtt, mint pl Collectors.toList (), nak, nek hívják.

Nézzünk meg egy példát, ahol először hívunk az egyes() közvetlenül a gyűjteményen, másodszor pedig egy párhuzamos folyamon:

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

Ha többször futtatjuk a kódot, akkor ezt látjuk list.forEach () az elemeket beszúrási sorrendben dolgozza fel, míg list.parallelStream (). forEach () minden futásnál más eredményt produkál.

Az egyik lehetséges kimenet:

ABCD CDBA

Egy másik:

ABCD DBCA

3.2. Egyedi iterátorok

Definiáljunk egy listát egy egyedi iterátorral, hogy fordított sorrendben ismételje meg a gyűjteményt:

class ReverseList kiterjeszti az ArrayList {@Override public Iterator iterator () {int startIndex = this.size () - 1; Lista lista = ez; Iterátor it = új Iterátor () {private int currentIndex = startIndex; @Orride public boolean hasNext () {return currentIndex> = 0; } @Orride public String next () {String next = list.get (currentIndex); currentIndex--; visszatérés következő; } @Orride public void remove () {dobj új UnsupportedOperationException (); }}; Hozd vissza; }} 

Amikor ismétlünk a listán, ismét a az egyes() közvetlenül a gyűjteményre, majd az adatfolyamra:

Listázza myList = új ReverseList (); myList.addAll (lista); myList.forEach (System.out :: print); System.out.print (""); myList.stream (). forEach (System.out :: print); 

Különböző eredményeket kapunk:

DCBA ABCD 

A különböző eredmények oka az az egyes() közvetlenül a listán használva használja az egyéni iterátort, míg stream (). forEach () egyszerűen felveszi az elemeket egyenként a listából, figyelmen kívül hagyva az iterátort.

4. A gyűjtemény módosítása

Sok gyűjtemény (pl. Tömb lista vagy HashSet) nem szabad strukturálisan módosítani, miközben iterálni kell rajtuk. Ha egy elemet eltávolítunk vagy hozzáadunk egy iteráció során, akkor kapunk egy ConcurrentModification kivétel.

Ezenkívül a gyűjteményeket hibamentesre tervezték, ami azt jelenti, hogy a kivétel azonnal bekerül, amint módosításra kerül sor.

Hasonlóképpen kapunk egy ConcurrentModification kivétel, amikor a stream folyamat végrehajtása során hozzáadunk vagy eltávolítunk egy elemet. A kivételt azonban később dobják.

Újabb finom különbség a kettő között az egyes() módszerek szerint a Java kifejezetten lehetővé teszi az elemek módosítását az iterátor segítségével. A folyamoknak ezzel szemben nem szabad beavatkozniuk.

Nézzük meg részletesebben az elemek eltávolítását és módosítását.

4.1. Elem eltávolítása

Határozzunk meg egy műveletet, amely eltávolítja a listánk utolsó elemét („D”):

Fogyasztó removeElement = s -> {System.out.println (s + "" + list.size ()); if (s! = null && s.egyenlő ("A")) {list.remove ("D"); }};

Amikor a listán ismétlünk, az első elem („A”) kinyomtatása után eltávolításra kerül:

list.forEach (removeElement);

Mivel az egyes() sikertelen, leállítjuk az iterációt és kivételt látunk a következő elem feldolgozása előtt:

4 kivétel a "main" szálban java.util.ConcurrentModificationException a java.util.ArrayList.forEach (ArrayList.java:1252) a ReverseList.main (ReverseList.java:1)

Lássuk, mi történik, ha használjuk stream (). forEach () helyette:

list.stream (). forEach (removeElement);

Itt folytatjuk az egész lista teljes körű ismétlését, mielőtt kivételt látnánk:

A 4 B 3 C 3 null 3 Kivétel a "main" témában .java: 580) a ReverseList.main oldalon (ReverseList.java:1)

A Java azonban nem garantálja, hogy a ConcurrentModificationException egyáltalán dobják. Ez azt jelenti, hogy soha nem szabad olyan programot írni, amely ettől a kivételtől függ.

4.2. Elemek váltása

Megváltoztathatunk egy elemet, miközben iterálunk egy listán:

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

Azonban bár nincs probléma ennek egyikével sem Collection.forEach () vagy stream (). forEach (), A Java megköveteli, hogy a folyam művelete ne legyen zavaró. Ez azt jelenti, hogy az elemeket nem szabad módosítani a folyamvezeték végrehajtása során.

Ennek oka az, hogy az adatfolyamnak elő kell segítenie a párhuzamos végrehajtást. Itt egy adatfolyam módosítása váratlan viselkedéshez vezethet.

5. Következtetés

Ebben a cikkben láttunk néhány példát, amelyek megmutatják a finom különbségeket Collection.forEach () és Collection.stream (). ForEach ().

Fontos azonban megjegyezni, hogy az összes fent bemutatott példa triviális, és csupán a két iterációs módszer összehasonlítására szolgál egy gyűjteményben. Nem szabad olyan kódot írni, amelynek helyessége a bemutatott viselkedésre támaszkodik.

Ha nem igényelünk adatfolyamot, hanem csak egy gyűjteményen szeretnénk iterálni, akkor az első választás a használat az egyes() közvetlenül a gyűjteményen.

A cikk példáinak forráskódja elérhető a GitHub oldalon.