Ghid pentru Java 8 pentru fiecare

1. Prezentare generală

Introdus în Java 8, bucla forEach oferă programatorilor un mod nou, concis și interesant de iterație asupra unei colecții .

În acest articol, vom vedea cum să folosim forEach cu colecții, ce fel de argument este nevoie și cum diferă această buclă de bucla for îmbunătățită .

Dacă aveți nevoie să aruncați câteva concepte despre Java 8, avem o colecție de articole care vă pot ajuta.

2. Bazele pentru fiecare

În Java, interfața de colecție are Iterable ca super interfață - și începând cu Java 8 această interfață are un nou API:

void forEach(Consumer action)

Pur și simplu, Javadoc-ul forEach afirmă„efectuează acțiunea dată pentru fiecare element al Iterabilului până când toate elementele au fost procesate sau acțiunea aruncă o excepție”.

Și astfel, cu forEach , putem repeta o colecție și efectua o acțiune dată pe fiecare element, ca orice alt Iterator.

De exemplu, o versiune pentru buclă a iterării și tipăririi unei colecții de șiruri :

for (String name : names) { System.out.println(name); }

Putem scrie acest lucru folosind forEach ca:

names.forEach(name -> { System.out.println(name); });

3. Folosind metoda forEach

Folosim forEach pentru a repeta o colecție și a efectua o anumită acțiune asupra fiecărui element. Acțiunea care trebuie efectuată este conținută într-o clasă care implementează interfața consumator și este transmisă pentru forEach ca argument.

Consumatorului Interfața este o interfață funcțională (o interfață cu o singură metodă abstractă). Acceptă o intrare și nu returnează niciun rezultat.

Iată definiția:

@FunctionalInterface public interface Consumer { void accept(T t); }

Prin urmare, orice implementare, de exemplu, un consumator care tipărește pur și simplu un șir :

Consumer printConsumer = new Consumer() { public void accept(String name) { System.out.println(name); }; };

poate fi transmis către forEach ca argument:

names.forEach(printConsumer);

Dar acesta nu este singurul mod de a crea o acțiune prin intermediul unui consumator și de a folosi forEach API.

Să vedem cele mai populare 3 moduri în care vom folosi metoda forEach :

3.1. Implementarea consumatorilor anonimi

Putem instanția o implementare a interfeței Consumer folosind o clasă anonimă și apoi să o aplicăm ca argument la metoda forEach :

Consumer printConsumer= new Consumer() { public void accept(String name) { System.out.println(name); } }; names.forEach(printConsumer);

Acest lucru funcționează bine, dar dacă analizăm exemplul de mai sus, vom vedea că partea reală care este de folos este codul din metoda accept () .

Deși expresiile Lambda sunt acum norma și modalitatea mai ușoară de a face acest lucru, merită totuși să știm cum să implementăm interfața consumator .

3.2. O expresie Lambda

Avantajul major al interfețelor funcționale Java 8 este că putem folosi expresii Lambda pentru a le instanția și pentru a evita utilizarea unor implementări voluminoase de clasă anonimă.

Deoarece Consumer Interface este o interfață funcțională, o putem exprima în Lambda sub forma:

(argument) -> { //body }

Prin urmare, printConsumer-ul nostru se simplifică la:

name -> System.out.println(name)

Și îl putem transmite pentru fiecare ca:

names.forEach(name -> System.out.println(name));

De la introducerea expresiilor Lambda în Java 8, acesta este probabil cel mai comun mod de a folosi metoda forEach .

Lambdas are o curbă de învățare foarte reală, așa că, dacă începeți, această scriere trece în revistă câteva bune practici de lucru a noii caracteristici de limbă.

3.3. O metodă de referință

Putem folosi sintaxa de referință a metodei în locul sintaxei normale Lambda în care există deja o metodă pentru a efectua o operație pe clasă:

names.forEach(System.out::println);

4. Lucrul cu forEach

4.1. Iterând peste o colecție

Orice iterabil de tip Colecție - listă, set, coadă etc. au aceeași sintaxă pentru utilizarea forEach.

Prin urmare, așa cum am văzut deja, pentru a itera elemente ale unei liste:

List names = Arrays.asList("Larry", "Steve", "James"); names.forEach(System.out::println);

În mod similar pentru un set:

Set uniqueNames = new HashSet(Arrays.asList("Larry", "Steve", "James")); uniqueNames.forEach(System.out::println);

Sau să spunem pentru o coadă care este, de asemenea, o colecție :

Queue namesQueue = new ArrayDeque(Arrays.asList("Larry", "Steve", "James")); namesQueue.forEach(System.out::println);

4.2. Iterarea peste o hartă - Utilizarea hărții pentru fiecare

Hărțile nu sunt Iterabile , dar oferă o variantă proprie de forEach care acceptă un BiConsumer .

Un BiConsumer a fost introdus în loc de Consumer în Iterable's forEach, astfel încât o acțiune să poată fi efectuată atât pe cheia cât și pe valoarea unei Hărți simultan.

Să creăm o hartă cu intrări:

Map namesMap = new HashMap(); namesMap.put(1, "Larry"); namesMap.put(2, "Steve"); namesMap.put(3, "James");

Apoi, să iterăm peste namesMap folosind Map's forEach :

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

După cum putem vedea aici, am folosit un BiConsumer :

(key, value) -> System.out.println(key + " " + value)

pentru a itera peste intrările Hărții .

4.3. Iterarea peste o hartă - prin Iterarea entrySet

De asemenea, putem itera setul de intrări al unei hărți folosind Iterable forEach.

Deoarece intrările unei hărți sunt stocate într-un set numit EntrySet, putem itera că folosind un forEach:

namesMap.entrySet().forEach(entry -> System.out.println( entry.getKey() + " " + entry.getValue()));

5. Foreach vs For-Loop

Dintr-un punct de vedere simplu, ambele bucle oferă aceeași funcționalitate - buclați prin elemente dintr-o colecție.

Principala diferență dintre cei doi este că sunt iteratori diferiți - bucla for îmbunătățită este un iterator extern, în timp ce noua metodă forEach este una internă .

5.1. Iterator intern - pentru fiecare

Acest tip de iterator gestionează iterația în fundal și lasă programatorul să codeze doar ceea ce se intenționează a fi făcut cu elementele colecției.

În schimb, iteratorul, gestionează iterația și se asigură că procesează elementele unul câte unul.

Să vedem un exemplu de iterator intern:

names.forEach(name -> System.out.println(name));

În metoda forEach de mai sus, putem vedea că argumentul furnizat este o expresie lambda. Acest lucru înseamnă că metoda trebuie doar să știe ce este de făcut și toată munca de iterație va fi îngrijită intern.

5.2. Iterator extern - pentru buclă

Iteratorii externi amestecă ce și cum trebuie realizată bucla.

Enumerările , iteratoarele și bucla for îmbunătățită sunt toate iteratoare externe (amintiți-vă metodele iterator (), next () sau hasNext () ?). În toate aceste iterații este treaba noastră să specificăm cum să efectuăm iterațiile.

Luați în considerare această buclă familiară:

for (String name : names) { System.out.println(name); }

Deși nu invocăm în mod explicit metodele hasNext () sau next () în timp ce iterați pe listă, codul de bază care face ca această iterație să funcționeze folosește aceste metode. Acest lucru implică faptul că complexitatea acestor operațiuni este ascunsă de programator, dar încă există.

Spre deosebire de un iterator intern în care colecția face iterația în sine, aici avem nevoie de cod extern care scoate fiecare element din colecție.

6. Concluzie

În acest articol, am arătat că bucla forEach este mai convenabilă decât bucla normală for-loop .

De asemenea, am văzut cum funcționează metoda forEach și ce tip de implementare poate primi ca argument pentru a efectua o acțiune asupra fiecărui element din colecție.

În cele din urmă, toate fragmentele utilizate în acest articol sunt disponibile în depozitul nostru Github.