707 Stimmen

Methoden zur Iteration über eine Liste in Java

Als relativ neuer Benutzer der Java-Sprache versuche ich mich mit all den Möglichkeiten (oder zumindest den nicht pathologischen) vertraut zu machen, wie man durch eine Liste (oder vielleicht andere Sammlungen) iterieren kann, und welche Vor- und Nachteile jede Methode hat.

Gegeben ein List list Objekt, kenne ich die folgenden Möglichkeiten, um durch alle Elemente zu iterieren:

Grundlegende for Schleife (natürlich gibt es auch entsprechende while / do while Schleifen)

// Nicht empfohlen (siehe unten)!
for (int i = 0; i < list.size(); i++) {
    E element = list.get(i);
    // 1 - Methoden des Elements aufrufen
    // 2 - 'i' verwenden, um indexbasierte Aufrufe von Methoden der Liste zu machen

    // ...
}

Anmerkung: Wie @amarseillan bemerkt hat, ist diese Form keine gute Wahl für die Iteration über Lists, da die tatsächliche Implementierung der get Methode möglicherweise nicht so effizient ist wie bei Verwendung eines Iterator. Zum Beispiel müssen LinkedList-Implementierungen alle Elemente vor i durchlaufen, um das i-te Element zu erhalten.

In obigem Beispiel gibt es für die Listenimplementierung keine Möglichkeit, "ihre Position zu speichern", um zukünftige Iterationen effizienter zu machen. Für ein ArrayList ist das nicht wirklich wichtig, weil die Komplexität / Kosten von get konstant sind (O(1)), während sie für ein LinkedList proportional zur Größe der Liste sind (O(n)).

Für weitere Informationen über die Rechenkomplexität der integrierten Collections-Implementierungen, schauen Sie sich diese Frage an.

Verbesserte for Schleife (gut erklärt in dieser Frage)

for (E element : list) {
    // 1 - Methoden des Elements aufrufen

    // ...
}

Iterator

for (Iterator iter = list.iterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - Methoden des Elements aufrufen
    // 2 - iter.remove() verwenden, um das aktuelle Element aus der Liste zu entfernen

    // ...
}

ListIterator

for (ListIterator iter = list.listIterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - Methoden des Elements aufrufen
    // 2 - iter.remove() verwenden, um das aktuelle Element aus der Liste zu entfernen
    // 3 - iter.add(...) verwenden, um ein neues Element in die Liste einzufügen
    //     zwischen element und iter->next()
    // 4 - iter.set(...) verwenden, um das aktuelle Element zu ersetzen

    // ...
}

Funktionales Java

list.stream().map(e -> e + 1); // Kann eine Transformationsfunktion für e anwenden

Iterable.forEach, Stream.forEach, ...

(Eine map-Methode aus der Stream-API von Java 8 (siehe Antwort von @i_am_zero).)

In Java 8 haben Sammlungsklassen, die Iterable implementieren (zum Beispiel alle Lists), nun eine forEach Methode, die anstelle der oben gezeigten for-Schleife verwendet werden kann. (Hier ist eine andere Frage, die einen guten Vergleich liefert.)

Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - Methoden eines Elements aufrufen
// 2 - wäre ein Verweis auf das enthaltende Objekt erforderlich, um ein Element zu entfernen
//     (TODO: Bitte jemand bestätigen / verneinen)
// 3 - trennt funktional die Iteration von der Aktion ab,
//     die mit jedem Element ausgeführt wird.

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Gleiche Möglichkeiten wie oben plus möglicherweise höhere
// Nutzung von Parallelität
// (Vorsicht: Als Konsequenz ist die Ausführungsreihenfolge nicht garantiert,
// siehe [Stream.forEachOrdered][stream-foreach-ordered] für weitere
// Informationen dazu).

Gibt es noch andere Möglichkeiten, wenn ja, welche?

(Übrigens, mein Interesse resultiert überhaupt nicht aus dem Wunsch, die Leistung zu optimieren; ich möchte nur wissen, welche Formen mir als Entwickler zur Verfügung stehen.)

330voto

Ted Hopp Punkte 227177

Die drei Formen des Schleifens sind nahezu identisch. Die verbesserte for-Schleife:

for (E element : list) {
    . . .
}

ist laut der Java Language Specification identisch in der Wirkung zur expliziten Verwendung eines Iterators mit einer traditionellen for-Schleife. Im dritten Fall können Sie den Listeninhalt nur ändern, indem Sie das aktuelle Element entfernen und dann nur, wenn Sie dies über die remove-Methode des Iterators selbst tun. Bei der indexbasierten Iteration können Sie die Liste beliebig ändern. Das Hinzufügen oder Entfernen von Elementen, die vor dem aktuellen Index kommen, birgt jedoch das Risiko, dass Ihre Schleife Elemente überspringt oder das gleiche Element mehrmals verarbeitet; Sie müssen den Schleifenzähler entsprechend anpassen, wenn Sie solche Änderungen vornehmen.

In allen Fällen ist element ein Verweis auf das tatsächliche Listenelement. Keine der Iterationsmethoden erstellt eine Kopie von etwas in der Liste. Änderungen am internen Zustand von element werden immer im internen Zustand des entsprechenden Elements in der Liste gesehen.

Grundsätzlich gibt es nur zwei Möglichkeiten, eine Liste zu durchlaufen: durch Verwendung eines Index oder durch Verwendung eines Iterators. Die erweiterte for-Schleife ist nur eine syntaktische Abkürzung, die in Java 5 eingeführt wurde, um die Mühe zu vermeiden, einen Iterator explizit zu definieren. Für beide Stile können Sie im Wesentlichen triviale Variationen mit for, while oder do while Blöcken erstellen, aber sie kommen letztendlich auf dasselbe heraus (oder besser gesagt, auf zwei Dinge).

EDIT: Wie @iX3 in einem Kommentar anmerkt, können Sie einen ListIterator verwenden, um das aktuelle Element einer Liste während der Iteration festzulegen. Sie müssten List#listIterator() anstelle von List#iterator() verwenden, um die Schleifenvariable zu initialisieren (die offensichtlich als ListIterator und nicht als Iterator deklariert werden müsste).

53voto

jacobq Punkte 10491

Beispiel jeder Art, die in der Frage aufgelistet ist:

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List numbers = new ArrayList();

        // füllt die Liste mit Anfangswerten
        for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
            numbers.add(i);
        printList(numbers);         // 0,1,2,3,4,5,6,7

        // ersetzt jedes Element durch das Doppelte seines Wertes
        for (int index=0; index < numbers.size(); index++) {
            numbers.set(index, numbers.get(index)*2); 
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // macht nichts, da die Liste nicht verändert wird
        for (Integer number : numbers) {
            number++; // number = new Integer(number+1);
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14  

        // das gleiche wie oben -- nur andere Syntax
        for (Iterator iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            number++;
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // ListIterator bietet eine "add" Methode zum Einfügen von Elementen
        // zwischen dem aktuellen Element und dem Cursor
        for (ListIterator iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.add(number+1);     // füge eine Zahl direkt davor ein
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        // Iterator bietet eine "remove" Methode zum Löschen von Elementen
        // zwischen dem aktuellen Element und dem Cursor
        for (Iterator iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            if (number % 2 == 0)    // wenn die Zahl gerade ist
                iter.remove();      // entferne sie aus der Sammlung
        }
        printList(numbers);         // 1,3,5,7,9,11,13,15

        // ListIterator bietet eine "set" Methode zum Ersetzen von Elementen
        for (ListIterator iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.set(number/2);     // jedes Element durch 2 teilen
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7
     }

     public static void printList(List numbers) {
        StringBuilder sb = new StringBuilder();
        for (Integer number : numbers) {
            sb.append(number);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length()-1); // entferne das abschließende Komma
        System.out.println(sb.toString());
     }
}

27voto

amarseillan Punkte 472

Die grundlegende Schleife wird nicht empfohlen, da Sie die Implementierung der Liste nicht kennen.

Wenn es sich um eine LinkedList handelte, würde jeder Aufruf von

list.get(i)

das Iterieren über die Liste bedeuten und zu einer Zeitkomplexität von N^2 führen.

26voto

eugene82 Punkte 7767

Eine JDK8-Style-Iteration:

public class IterationDemo {

    public static void main(String[] args) {
        List list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("Element " + elem));
    }
}

9voto

akhil_mittal Punkte 20953

In Java 8 haben wir mehrere Möglichkeiten, über Sammlungsklassen zu iterieren.

Verwendung von Iterable forEach

Die Kollektionen, die Iterable implementieren (zum Beispiel alle Listen), haben jetzt eine forEach-Methode. Wir können Methodenreferenzen verwenden, die in Java 8 eingeführt wurden.

Arrays.asList(1,2,3,4).forEach(System.out::println);

Verwendung von Streams forEach und forEachOrdered

Wir können auch über eine Liste mit einem Stream iterieren, wie folgt:

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);

Wir sollten forEachOrdered gegenüber forEach bevorzugen, da das Verhalten von forEach explizit nicht deterministisch ist, während forEachOrdered eine Aktion für jedes Element dieses Streams in der Begegnungsreihenfolge des Streams ausführt, wenn der Stream eine definierte Begegnungsreihenfolge hat. Daher garantiert forEach nicht, dass die Reihenfolge eingehalten wird.

Der Vorteil von Streams besteht darin, dass wir auch bei Bedarf parallele Streams verwenden können. Wenn das Ziel nur darin besteht, die Elemente unabhängig von der Reihenfolge zu drucken, können wir parallele streams verwenden, wie folgt:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X