Anleitung zu Java 8 forEach

1. Übersicht

Die in Java 8 eingeführte forEach- Schleife bietet Programmierern eine neue, präzise und interessante Möglichkeit, eine Sammlung zu durchlaufen .

In diesem Artikel erfahren Sie, wie Sie forEach mit Sammlungen verwenden, welche Art von Argument erforderlich ist und wie sich diese Schleife von der erweiterten for-Schleife unterscheidet .

Wenn Sie einige Konzepte von Java 8 auffrischen müssen, haben wir eine Sammlung von Artikeln, die Ihnen helfen können.

2. Grundlagen von forEach

In Java hat die Collection- Schnittstelle Iterable als Super-Schnittstelle - und ab Java 8 verfügt diese Schnittstelle über eine neue API:

void forEach(Consumer action)

Einfach ausgedrückt, das Javadoc von forEach gibt an, dass es "die angegebene Aktion für jedes Element der Iterable ausführt, bis alle Elemente verarbeitet wurden oder die Aktion eine Ausnahme auslöst".

Mit forEach können wir also eine Sammlung durchlaufen und wie bei jedem anderen Iterator eine bestimmte Aktion für jedes Element ausführen .

Beispiel: Eine For-Loop- Version zum Iterieren und Drucken einer Sammlung von Zeichenfolgen :

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

Wir können dies mit forEach schreiben als:

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

3. Verwenden der forEach- Methode

Wir verwenden forEach , um eine Sammlung zu durchlaufen und für jedes Element eine bestimmte Aktion auszuführen. Die auszuführende Aktion ist in einer Klasse enthalten, die die Consumer- Schnittstelle implementiert , und wird als Argument an forEach übergeben .

Die Consumer- Schnittstelle ist eine funktionale Schnittstelle (eine Schnittstelle mit einer einzelnen abstrakten Methode). Es akzeptiert eine Eingabe und gibt kein Ergebnis zurück.

Hier ist die Definition:

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

Daher kann jede Implementierung, beispielsweise ein Consumer, der einfach einen String druckt :

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

kann als Argument an forEach übergeben werden :

names.forEach(printConsumer);

Dies ist jedoch nicht die einzige Möglichkeit, eine Aktion über einen Consumer zu erstellen und die forEach- API zu verwenden.

Sehen wir uns die drei beliebtesten Methoden an, mit denen wir die forEach- Methode verwenden:

3.1. Anonyme Consumer- Implementierung

Wir können eine Implementierung der Consumer- Schnittstelle mithilfe einer anonymen Klasse instanziieren und sie dann als Argument auf die forEach- Methode anwenden :

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

Dies funktioniert gut, aber wenn wir das obige Beispiel analysieren, werden wir sehen, dass der tatsächlich verwendete Teil der Code in der accept () -Methode ist.

Obwohl Lambda-Ausdrücke heute die Norm und der einfachere Weg sind, lohnt es sich dennoch zu wissen, wie die Consumer- Schnittstelle implementiert wird.

3.2. Ein Lambda-Ausdruck

Der Hauptvorteil von Java 8-Funktionsschnittstellen besteht darin, dass wir Lambda-Ausdrücke verwenden können, um sie zu instanziieren und die Verwendung umfangreicher anonymer Klassenimplementierungen zu vermeiden.

Da Consumer Interface eine funktionale Schnittstelle ist, können wir sie in Lambda in Form von:

(argument) -> { //body }

Daher vereinfacht unser printConsumer Folgendes :

name -> System.out.println(name)

Und wir können es an ForEach weitergeben als:

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

Seit der Einführung von Lambda-Ausdrücken in Java 8 ist dies wahrscheinlich die häufigste Methode zur Verwendung der forEach- Methode.

Lambdas haben eine sehr reale Lernkurve. Wenn Sie also anfangen, werden in diesem Artikel einige bewährte Methoden für die Arbeit mit der neuen Sprachfunktion beschrieben.

3.3. Eine Methodenreferenz

Wir können die Methodenreferenzsyntax anstelle der normalen Lambda-Syntax verwenden, bei der bereits eine Methode zum Ausführen einer Operation für die Klasse vorhanden ist:

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

4. Mit forEach arbeiten

4.1. Durch eine Sammlung iterieren

Alle iterierbaren Elemente vom Typ Sammlung - Liste, Satz, Warteschlange usw. haben dieselbe Syntax für die Verwendung von forEach.

Um, wie wir bereits gesehen haben, Elemente einer Liste zu iterieren:

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

Ähnliches gilt für einen Satz:

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

Oder sagen wir für eine Warteschlange, die auch eine Sammlung ist :

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

4.2. Iterieren über eine Karte - Verwenden von Map's forEach

Karten sind nicht iterierbar , bieten jedoch eine eigene Variante von forEach , die einen BiConsumer akzeptiert .

Ein BiConsumer wurde statt eingeführt Consumer in Iterable der forEach so dass eine Aktion kann den Schlüssel und den Wert einer auf beiden erfolgen Karte gleichzeitig.

Erstellen wir eine Karte mit Einträgen:

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

Lassen Sie uns als Nächstes mit Map's forEach über namesMap iterieren :

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

Wie wir hier sehen können, haben wir einen BiConsumer verwendet :

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

über die Einträge der Karte zu iterieren .

4.3. Iterieren über eine Karte - durch Iterieren von entrySet

Wir können das EntrySet einer Karte auch mit Iterables forEach iterieren .

Da die Einträge einer Karte in einem Set namens EntrySet gespeichert sind , können wir dies mit forEach wiederholen :

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

5. Foreach vs For-Loop

Aus einer einfachen Sicht bieten beide Schleifen die gleiche Funktionalität - Schleife durch Elemente in einer Sammlung.

Der Hauptunterschied zwischen den beiden besteht darin, dass sie unterschiedliche Iteratoren sind - die erweiterte for-Schleife ist ein externer Iterator, während die neue forEach- Methode ein interner ist .

5.1. Interner Iterator - für jeden

Diese Art von Iterator verwaltet die Iteration im Hintergrund und überlässt es dem Programmierer, nur zu codieren, was mit den Elementen der Sammlung geschehen soll.

Der Iterator verwaltet stattdessen die Iteration und stellt sicher, dass die Elemente einzeln verarbeitet werden.

Sehen wir uns ein Beispiel für einen internen Iterator an:

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

In der obigen forEach- Methode können wir sehen, dass das angegebene Argument ein Lambda-Ausdruck ist. Dies bedeutet, dass die Methode nur wissen muss, was zu tun ist, und dass die gesamte Iterationsarbeit intern erledigt wird.

5.2. Externer Iterator - for-Schleife

Externe Iteratoren mischen das Was und Wie, wie die Schleife ausgeführt werden soll.

Aufzählungen , Iteratoren und erweiterte for-Schleifen sind externe Iteratoren (erinnern Sie sich an die Methoden iterator (), next () oder hasNext () ?). In all diesen Iteratoren ist es unsere Aufgabe, festzulegen, wie Iterationen durchgeführt werden sollen.

Betrachten Sie diese bekannte Schleife:

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

Obwohl wir beim Durchlaufen der Liste nicht explizit die Methoden hasNext () oder next () aufrufen , verwendet der zugrunde liegende Code, mit dem diese Iteration funktioniert, diese Methoden. Dies bedeutet, dass die Komplexität dieser Operationen dem Programmierer verborgen bleibt, aber immer noch vorhanden ist.

Im Gegensatz zu einem internen Iterator, in dem die Sammlung die Iteration selbst ausführt, benötigen wir hier externen Code, der jedes Element aus der Sammlung entfernt.

6. Fazit

In diesem Artikel haben wir gezeigt, dass die forEach- Schleife bequemer ist als die normale for-Schleife .

Wir haben auch gesehen, wie die forEach- Methode funktioniert und welche Art von Implementierung als Argument empfangen werden kann, um eine Aktion für jedes Element in der Auflistung auszuführen.

Schließlich sind alle in diesem Artikel verwendeten Snippets in unserem Github-Repository verfügbar.