So greifen Sie auf einen Iterationszähler in einer For Each-Schleife zu

1. Übersicht

Während der Iteration über Daten in Java möchten wir möglicherweise sowohl auf das aktuelle Element als auch auf seine Position in der Datenquelle zugreifen.

Dies ist in einer klassischen for- Schleife sehr einfach zu erreichen , bei der die Position normalerweise im Mittelpunkt der Berechnungen der Schleife steht. Es erfordert jedoch etwas mehr Arbeit, wenn wir Konstrukte wie für jede Schleife oder jeden Stream verwenden.

In diesem kurzen Tutorial sehen wir uns einige Möglichkeiten an, wie für jede Operation ein Zähler enthalten sein kann.

2. Implementierung eines Zählers

Beginnen wir mit einem einfachen Beispiel. Wir nehmen eine geordnete Liste von Filmen und geben sie mit ihrem Ranking aus.

List IMDB_TOP_MOVIES = Arrays.asList("The Shawshank Redemption", "The Godfather", "The Godfather II", "The Dark Knight");

2.1. für Schleife

Eine for- Schleife verwendet einen Zähler, um auf das aktuelle Element zu verweisen. Auf diese Weise können Sie auf einfache Weise sowohl die Daten als auch deren Index in der Liste verarbeiten:

List rankings = new ArrayList(); for (int i = 0; i < movies.size(); i++) { String ranking = (i + 1) + ": " + movies.get(i); rankings.add(ranking); }

Da es sich bei dieser Liste wahrscheinlich um eine ArrayList handelt , ist die get- Operation effizient und der obige Code ist eine einfache Lösung für unser Problem.

assertThat(getRankingsWithForLoop(IMDB_TOP_MOVIES)) .containsExactly("1: The Shawshank Redemption", "2: The Godfather", "3: The Godfather II", "4: The Dark Knight");

Auf diese Weise können jedoch nicht alle Datenquellen in Java iteriert werden. Manchmal ist get eine zeitintensive Operation, oder wir können das nächste Element einer Datenquelle nur mit Stream oder Iterable verarbeiten.

2.2. für jede Schleife

Wir werden unsere Liste der Filme weiterhin verwenden, aber tun wir so, als könnten wir sie nur mit Java für jedes Konstrukt durchlaufen:

for (String movie : IMDB_TOP_MOVIES) { // use movie value }

Hier müssen wir eine separate Variable verwenden, um den aktuellen Index zu verfolgen. Wir können das außerhalb der Schleife konstruieren und innerhalb erhöhen:

int i = 0; for (String movie : movies) { String ranking = (i + 1) + ": " + movie; rankings.add(ranking); i++; }

Wir sollten beachten, dass wir den Zähler erhöhen müssen, nachdem er in der Schleife verwendet wurde.

3. Eine Funktion für jeden

Wenn Sie die Zählererweiterung jedes Mal schreiben, wenn sie benötigt wird, kann dies zu einer Duplizierung des Codes führen und zu versehentlichen Fehlern beim Aktualisieren der Zählervariablen führen. Wir können daher das Obige unter Verwendung der Funktionsschnittstellen von Java verallgemeinern.

Zunächst sollten wir uns das Verhalten innerhalb der Schleife als Konsument sowohl des Elements in der Sammlung als auch des Index vorstellen. Dies kann mit BiConsumer modelliert werden , das eine Akzeptanzfunktion definiert , die zwei Parameter akzeptiert

@FunctionalInterface public interface BiConsumer { void accept(T t, U u); }

Da das Innere unserer Schleife zwei Werte verwendet, können wir eine allgemeine Schleifenoperation schreiben. Es könnte die Iterable der Quelldaten, über die die Schleife für jede Schleife ausgeführt wird, und den BiConsumer für die Operation für jedes Element und seinen Index verwenden. Wir können dies mit dem Typparameter T generisch machen :

static  void forEachWithCounter(Iterable source, BiConsumer consumer) { int i = 0; for (T item : source) { consumer.accept(i, item); i++; } }

Wir können dies mit unserem Beispiel für Filmrankings verwenden, indem wir die Implementierung für den BiConsumer als Lambda bereitstellen :

List rankings = new ArrayList(); forEachWithCounter(movies, (i, movie) -> { String ranking = (i + 1) + ": " + movies.get(i); rankings.add(ranking); });

4. Hinzufügen eines Zählers zu forEach with Stream

Mit der Java Stream- API können wir ausdrücken, wie unsere Daten Filter und Transformationen durchlaufen. Es bietet auch eine forEach- Funktion. Versuchen wir, dies in eine Operation umzuwandeln, die den Zähler enthält.

Die Funktion Stream forEach benötigt einen Consumer , um das nächste Element zu verarbeiten. Wir könnten diesen Verbraucher jedoch dazu bringen, den Zähler im Auge zu behalten und den Artikel an einen BiConsumer weiterzuleiten :

public static  Consumer withCounter(BiConsumer consumer) { AtomicInteger counter = new AtomicInteger(0); return item -> consumer.accept(counter.getAndIncrement(), item); }

Diese Funktion gibt ein neues Lambda zurück. Dieses Lambda verwendet das AtomicInteger- Objekt, um den Zähler während der Iteration zu verfolgen. Die Funktion getAndIncrement wird jedes Mal aufgerufen, wenn ein neues Element vorhanden ist.

Das von dieser Funktion erstellte Lambda wird an den übergebenen BiConsumer delegiert, damit der Algorithmus sowohl das Element als auch seinen Index verarbeiten kann.

Sehen wir uns dies in unserem Filmranking-Beispiel für einen Stream namens " Filme" an :

List rankings = new ArrayList(); movies.forEach(withCounter((i, movie) -> { String ranking = (i + 1) + ": " + movie; rankings.add(ranking); }));

In forEach wird die withCounter- Funktion aufgerufen , um ein Objekt zu erstellen, das sowohl die Anzahl verfolgt als auch als Consumer fungiert , bei dem die forEach- Operation auch ihre Werte übergibt.

5. Schlussfolgerung

In diesem kurzen Artikel haben wir drei Möglichkeiten untersucht, wie Sie für jede Operation einen Zähler an Java anhängen können .

Wir haben gesehen, wie der Index des aktuellen Elements bei jeder Implementierung für eine Schleife verfolgt wird. Wir haben uns dann angesehen, wie dieses Muster verallgemeinert und zu Streaming-Vorgängen hinzugefügt werden kann.

Wie immer ist der Beispielcode für diesen Artikel auf GitHub verfügbar.