Forskjellen mellom Collection.stream (). ForEach () og Collection.forEach ()

1. Introduksjon

Det er flere alternativer for å gjenta over en samling i Java. I denne korte opplæringen vil vi se på to lignende utseende - Collection.stream (). ForEach () og Collection.forEach ().

I de fleste tilfeller vil begge gi de samme resultatene, men det er noen subtile forskjeller vi vil se på.

2. Oversikt

La oss først lage en liste for å gjenta:

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

Den enkleste måten er å bruke den forbedrede for-loop:

for (String s: list) {// gjør noe med s} 

Hvis vi vil bruke Java i funksjonell stil, kan vi også bruke for hver(). Vi kan gjøre det direkte på samlingen:

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

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

list.stream (). forEach (forbruker); 

Begge versjonene vil gjentas over listen og skrive ut alle elementene:

ABCD ABCD

I dette enkle tilfellet gjør det ikke noen forskjell hvilken for hver() vi bruker.

3. Utførelsespålegg

Collection.forEach () bruker samlingens iterator (hvis en er spesifisert). Det betyr at behandlingsordren til varene er definert. Derimot behandlingsordren til Collection.stream (). ForEach () er udefinert.

I de fleste tilfeller gjør det ingen forskjell hvilken av de to vi velger.

3.1. Parallelle bekker

Parallelle strømmer tillater oss å utføre strømmen i flere tråder, og i slike situasjoner er utførelsesordren udefinert. Java krever bare at alle tråder er ferdige før terminaloperasjoner, for eksempel Collectors.toList (), er kalt.

La oss se på et eksempel der vi først ringer for hver() direkte på samlingen, og for det andre i en parallell strøm:

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

Hvis vi kjører koden flere ganger, ser vi det list.forEach () behandler elementene i innsettingsrekkefølge, mens list.parallelStream (). forEach () gir et annet resultat ved hvert løp.

En mulig utgang er:

ABCD CDBA

En annen er:

ABCD DBCA

3.2. Egendefinerte Iteratorer

La oss definere en liste med en tilpasset iterator som skal gjentas over samlingen i omvendt rekkefølge:

klasse ReverseList utvider ArrayList {@Override public Iterator iterator () {int startIndex = this.size () - 1; Listeliste = dette; Iterator it = new Iterator () {private int currentIndex = startIndex; @Override public boolean hasNext () {return currentIndex> = 0; } @ Override public String next () {String next = list.get (currentIndex); currentIndex--; tilbake neste; } @ Override public void remove () {throw new UnsupportedOperationException (); }}; Returner det; }} 

Når vi gjentar oss over listen, igjen med for hver() direkte på samlingen og deretter på strømmen:

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

Vi får forskjellige resultater:

DCBA ABCD 

Årsaken til de forskjellige resultatene er at for hver() brukes direkte på listen bruker den tilpassede iteratoren, samtidig som stream (). forEach () tar ganske enkelt elementer en etter en fra listen, og ignorerer iteratoren.

4. Modifikasjon av samlingen

Mange samlinger (f.eks. ArrayList eller HashSet) bør ikke endres strukturelt mens det gjentas over dem. Hvis et element fjernes eller legges til under en iterasjon, får vi et ConcurrentModification unntak.

Videre er samlinger designet for å feile raskt, noe som betyr at unntaket blir kastet så snart det er en modifikasjon.

På samme måte får vi en ConcurrentModification unntak når vi legger til eller fjerner et element under utførelsen av strømrørledningen. Unntaket vil imidlertid bli kastet senere.

Nok en subtil forskjell mellom de to for hver() metoder er at Java eksplisitt tillater endring av elementer ved hjelp av iteratoren. Strømmer, derimot, bør ikke være forstyrrende.

La oss se på å fjerne og endre elementer mer detaljert.

4.1. Fjerne et element

La oss definere en operasjon som fjerner det siste elementet (“D”) i listen vår:

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

Når vi gjentas over listen, fjernes det siste elementet etter at det første elementet (“A”) er skrevet ut:

list.forEach (removeElement);

Siden for hver() er feilrask, slutter vi å itere og ser et unntak før neste element behandles:

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

La oss se hva som skjer hvis vi bruker stream (). forEach () i stedet:

list.stream (). forEach (removeElement);

Her fortsetter vi å gjenta hele listen før vi ser et unntak:

A 4 B 3 C 3 null 3 Unntak 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 imidlertid ikke at a ConcurrentModificationException kastes i det hele tatt. Det betyr at vi aldri skal skrive et program som er avhengig av dette unntaket.

4.2. Endring av elementer

Vi kan endre et element mens det gjentas over en liste:

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

Imidlertid er det ikke noe problem å gjøre dette ved hjelp av en av dem Collection.forEach () eller stream (). forEach (), Java krever en operasjon på en strøm for ikke å være forstyrrende. Dette betyr at elementer ikke skal endres under kjøringen av strømrørledningen.

Årsaken bak dette er at strømmen skal legge til rette for parallell gjennomføring. Her kan endring av elementer i en strøm føre til uventet oppførsel.

5. Konklusjon

I denne artikkelen så vi noen eksempler som viser de subtile forskjellene mellom Collection.forEach () og Collection.stream (). ForEach ().

Det er imidlertid viktig å merke seg at alle eksemplene som er vist ovenfor er trivielle og kun ment å sammenligne de to måtene å itere over en samling. Vi bør ikke skrive kode hvis korrekthet er avhengig av den viste oppførselen.

Hvis vi ikke trenger en strøm, men bare vil gjenta over en samling, bør førstevalget være å bruke for hver() direkte på samlingen.

Kildekoden for eksemplene i denne artikkelen er tilgjengelig på GitHub.