Forskellen mellem Collection.stream (). ForEach () og Collection.forEach ()

1. Introduktion

Der er flere muligheder for at gentage en samling i Java. I denne korte vejledning ser vi på to lignende udseende - Collection.stream (). ForEach () og Collection.forEach ().

I de fleste tilfælde vil begge give de samme resultater, men der er nogle subtile forskelle, vi vil se på.

2. Oversigt

Lad os først oprette en liste, der skal gentages:

Liste liste = Arrays.asList ("A", "B", "C", "D");

Den mest ligefremme måde er at bruge den forbedrede for-loop:

for (String s: liste) {// gør noget med s} 

Hvis vi vil bruge Java i funktionel stil, kan vi også bruge for hver(). Vi kan gøre det direkte på samlingen:

Forbrugerforbruger = s -> {System.out :: println}; list.forEach (forbruger); 

Eller vi kan ringe for hver() på samlingens strøm:

list.stream (). forEach (forbruger); 

Begge versioner gentages over listen og udskriver alle elementer:

ABCD ABCD

I dette enkle tilfælde betyder det ikke en forskel, hvilken for hver() vi bruger.

3. Udførelsesordre

Collection.forEach () bruger samlingens iterator (hvis en er angivet). Det betyder, at behandlingsordren på varerne er defineret. I modsætning hertil er behandlingsordren på Collection.stream (). ForEach () er udefineret.

I de fleste tilfælde gør det ikke en forskel, hvilken af ​​de to vi vælger.

3.1. Parallelle strømme

Parallelle strømme giver os mulighed for at udføre strømmen i flere tråde, og i sådanne situationer er udførelsesordren udefineret. Java kræver kun, at alle tråde er færdige før enhver terminalhandling, f.eks Collectors.toList (), Hedder.

Lad os se på et eksempel, hvor vi først kalder for hver() direkte på samlingen og for det andet på en parallel stream:

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

Hvis vi kører koden flere gange, ser vi det list.forEach () behandler elementerne i indsætningsrækkefølge, mens list.parallelStream (). forEach () producerer et andet resultat ved hver kørsel.

En mulig output er:

ABCD CDBA

En anden er:

ABCD DBCA

3.2. Brugerdefinerede Iteratorer

Lad os definere en liste med en brugerdefineret iterator, der skal gentages over samlingen i omvendt rækkefølge:

klasse ReverseList udvider ArrayList {@ Override public Iterator iterator () {int startIndex = this.size () - 1; Liste liste = dette; Iterator it = ny Iterator () {private int currentIndex = startIndex; @ Override offentlig boolsk hasNext () {return currentIndex> = 0; } @ Override public String next () {String next = list.get (currentIndex); currentIndex--; vende tilbage næste; } @ Overstyr offentlig tomrum fjern () {smid nyt UnsupportedOperationException (); }}; returner det; }} 

Når vi gentager listen, igen med for hver() direkte på samlingen og derefter på streamen:

Liste myList = ny ReverseList (); myList.addAll (liste); myList.forEach (System.out :: print); System.out.print (""); myList.stream (). forEach (System.out :: print); 

Vi får forskellige resultater:

DCBA ABCD 

Årsagen til de forskellige resultater er, at for hver() bruges direkte på listen bruger den brugerdefinerede iterator, mens stream (). forEach () tager simpelthen elementer en efter en fra listen og ignorerer iteratoren.

4. Ændring af samlingen

Mange samlinger (f.eks. ArrayList eller HashSet) bør ikke ændres strukturelt, mens det gentages over dem. Hvis et element fjernes eller tilføjes under en iteration, får vi et ConcurrentModification undtagelse.

Desuden er samlinger designet til at fejle hurtigt, hvilket betyder, at undtagelsen kastes, så snart der er en ændring.

På samme måde får vi en ConcurrentModification undtagelse, når vi tilføjer eller fjerner et element under udførelsen af ​​stream-pipelinen. Undtagelsen kastes dog senere.

En anden subtil forskel mellem de to for hver() metoder er, at Java eksplicit tillader ændringer af elementer ved hjælp af iteratoren. Strømme skal derimod ikke blande sig.

Lad os se nærmere på fjernelse og ændring af elementer.

4.1. Fjernelse af et element

Lad os definere en operation, der fjerner det sidste element (“D”) på vores liste:

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

Når vi gentager listen, fjernes det sidste element efter udskrivning af det første element (“A”):

list.forEach (removeElement);

Siden for hver() er fail-fast, stopper vi iterering og ser en undtagelse, før det næste element behandles:

En 4 undtagelse i tråden "main" java.util.ConcurrentModificationException på java.util.ArrayList.forEach (ArrayList.java:1252) på ReverseList.main (ReverseList.java:1)

Lad os se, hvad der sker, hvis vi bruger stream (). forEach () i stedet:

list.stream (). forEach (removeElement);

Her fortsætter vi med at gentage hele listen, før vi ser en undtagelse:

A 4 B 3 C 3 null 3 Undtagelse i tråden "main" java.util.ConcurrentModificationException at java.util.ArrayList $ ArrayListSpliterator.forEachRemaining (ArrayList.java:1380) på java.util.stream.ReferencePipeline $ Head.forEach (ReferencePipeline .java: 580) på ReverseList.main (ReverseList.java:1)

Java garanterer dog ikke, at en ConcurrentModificationException kastes overhovedet. Det betyder, at vi aldrig skal skrive et program, der afhænger af denne undtagelse.

4.2. Ændring af elementer

Vi kan ændre et element, mens det gentager sig over en liste:

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

Imidlertid er der ikke noget problem med at gøre dette ved hjælp af en af ​​dem Collection.forEach () eller stream (). forEach (), Java kræver en operation på en stream for ikke at blande sig. Dette betyder, at elementer ikke skal ændres under udførelsen af ​​stream-pipelinen.

Årsagen bag dette er, at strømmen skal lette parallel udførelse. Her kan ændring af elementer i en stream føre til uventet opførsel.

5. Konklusion

I denne artikel så vi nogle eksempler, der viser de subtile forskelle mellem Collection.forEach () og Collection.stream (). ForEach ().

Det er dog vigtigt at bemærke, at alle eksemplerne vist ovenfor er trivielle og kun er beregnet til at sammenligne de to måder at gentage sig over en samling. Vi bør ikke skrive kode, hvis korrekthed er afhængig af den viste adfærd.

Hvis vi ikke har brug for en stream, men kun vil gentage en samling, skal det første valg være at bruge for hver() direkte på samlingen.

Kildekoden til eksemplerne i denne artikel er tilgængelig på GitHub.