wake-up-neo.com

Möglichkeiten zum Durchlaufen einer Liste in Java

Ich bin ein wenig neu in der Java Sprache und versuche mich mit allen (oder zumindest nicht pathologischen) Möglichkeiten vertraut zu machen, die man durch eine Liste (oder vielleicht auch andere Sammlungen) und die Vorteile durchlaufen könnte oder Nachteile von jedem.

Wenn ich ein List<E> list -Objekt habe, kenne ich die folgenden Möglichkeiten, um alle Elemente zu durchlaufen:

Basic fürSchleife (natürlich gibt es auch entsprechende while/do while-Schleifen)

// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
    E element = list.get(i);
    // 1 - can call methods of element
    // 2 - can use 'i' to make index-based calls to methods of list

    // ...
}

Hinweis: Wie @amarseillan hervorhob, ist dieses Formular eine schlechte Wahl für das Iterieren über Lists, da die tatsächliche Implementierung der get -Methode möglicherweise nicht so effizient ist wie bei Verwendung einer Iterator. Beispielsweise müssen LinkedList Implementierungen alle Elemente vor i durchlaufen, um das i-te Element zu erhalten.

Im obigen Beispiel kann die List -Implementierung nicht "ihren Platz retten", um zukünftige Iterationen effizienter zu gestalten. Für ein ArrayList spielt es keine Rolle, da die Komplexität/Kosten von get eine konstante Zeit (O (1)) sind, während sie für ein LinkedList proportional zur Größe des ist Liste (O (n)).

Weitere Informationen zur Rechenkomplexität der integrierten Collections -Implementierungen finden Sie unter diese Frage .

Enhanced for loop (gut erklärt in dieser Frage )

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

Iterator

for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list

    // ...
}

ListIterator

for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list
    // 3 - can use iter.add(...) to insert a new element into the list
    //     between element and iter->next()
    // 4 - can use iter.set(...) to replace the current element

    // ...
}

Funktionales Java

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach , Stream.forEach , ...

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

In Java 8 Auflistungsklassen, die Iterable implementieren (z. B. alle List), ist jetzt eine forEach -Methode verfügbar, die anstelle der -Methode) verwendet werden kann. for loop statement wie oben gezeigt. (Hier ist eine andere Frage , die einen guten Vergleich liefert.)

Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
//     (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
//     being performed with each item.

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).

Welche anderen Möglichkeiten gibt es, wenn überhaupt?

(Übrigens entspringt mein Interesse keineswegs dem Wunsch, die Leistung zu optimieren ; ich möchte nur wissen, welche Formulare mir als Entwickler zur Verfügung stehen.)

532
iX3

Die drei Schleifenformen sind nahezu identisch. Die erweiterte for-Schleife:

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

ist laut Java Language Specification identisch mit der 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 nur dann, wenn Sie dies über die Methode remove des Iterators selbst tun. Bei einer indexbasierten Iteration können Sie die Liste beliebig ändern. Wenn Sie jedoch Elemente hinzufügen oder entfernen, die vor dem aktuellen Index liegen, besteht die Gefahr, dass Ihre Schleife Elemente überspringt oder dasselbe Element mehrmals verarbeitet wird. Sie müssen den Schleifenindex korrekt anpassen, wenn Sie solche Änderungen vornehmen.

In allen Fällen ist element ein Verweis auf das tatsächliche Listenelement. Bei keiner der Iterationsmethoden wird eine Kopie der Liste erstellt. Änderungen am internen Status von element werden immer im internen Status des entsprechenden Elements in der Liste angezeigt.

Im Wesentlichen gibt es nur zwei Möglichkeiten, eine Liste zu durchlaufen: mithilfe eines Index oder mithilfe eines Iterators. Die erweiterte for-Schleife ist nur eine syntaktische Abkürzung, die in Java 5 eingeführt wurde, um den Aufwand zu vermeiden, einen Iterator explizit zu definieren. Für beide Stile können Sie mit den Blöcken for, while oder do while im Wesentlichen triviale Variationen erstellen, die sich jedoch alle auf dasselbe (oder vielmehr auf zwei Dinge) beschränken.

BEARBEITEN: Wie @ iX3 in einem Kommentar hervorhebt, können Sie mit ListIterator das aktuelle Element einer Liste festlegen, während Sie iterieren. 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).

245
Ted Hopp

Beispiel für jede in der Frage aufgeführte Art:

ListIterationExample.Java

import Java.util.*;

public class ListIterationExample {

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

        // populates list with initial values
        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

        // replaces each element with twice its value
        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

        // does nothing because list is not being changed
        for (Integer number : numbers) {
            number++; // number = new Integer(number+1);
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14  

        // same as above -- just different syntax
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            number++;
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // ListIterator<?> provides an "add" method to insert elements
        // between the current element and the cursor
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.add(number+1);     // insert a number right before this
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        // Iterator<?> provides a "remove" method to delete elements
        // between the current element and the cursor
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            if (number % 2 == 0)    // if number is even 
                iter.remove();      // remove it from the collection
        }
        printList(numbers);         // 1,3,5,7,9,11,13,15

        // ListIterator<?> provides a "set" method to replace elements
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.set(number/2);     // divide each element by 2
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7
     }

     public static void printList(List<Integer> numbers) {
        StringBuilder sb = new StringBuilder();
        for (Integer number : numbers) {
            sb.append(number);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length()-1); // remove trailing comma
        System.out.println(sb.toString());
     }
}
43
iX3

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

Wenn das eine LinkedList war, jeder Aufruf von

list.get(i)

würde die Liste durchlaufen, was zu N ^ 2 Zeitkomplexität führen würde.

20
amarseillan

Eine JDK8-artige Iteration:

public class IterationDemo {

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

In Java 8 haben wir mehrere Möglichkeiten, Collection-Klassen zu durchlaufen.

Verwenden von Iterable forEach

Die Sammlungen, die Iterable implementieren (zum Beispiel alle Listen), haben jetzt die Methode forEach. Wir können Methodenreferenz verwenden, das in Java 8 eingeführt wurde.

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

Streams forEach und forEachOrdered verwenden

Wir können auch über eine Liste iterieren, indem wir Stream wie folgt verwenden:

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 vor forEach bevorzugen, da das Verhalten von forEach explizit nicht deterministisch ist, wenn forEachOrdered für jedes Element dieses Streams eine Aktion in der Reihenfolge der Begegnung ausführt stream wenn der stream eine definierte begegnungsreihenfolge hat. ForEach garantiert daher nicht, dass die Bestellung erhalten bleibt.

Der Vorteil von Streams ist, dass wir bei Bedarf auch parallele Streams verwenden können. Wenn das Ziel nur darin besteht, die Artikel unabhängig von der Bestellung zu drucken, können wir den parallelen Stream wie folgt verwenden:

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

Ich weiß nicht, was Sie für pathologisch halten, aber lassen Sie mich einige Alternativen nennen, die Sie vorher nicht gesehen haben könnten:

List<E> sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

Oder seine rekursive Version:

void visit(List<E> list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

Auch eine rekursive Version des klassischen for(int i=0...:

void visit(List<E> list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

Ich erwähne sie, weil Sie "etwas neu in Java" sind und dies interessant sein könnte.

4
Mario Rossi

Sie können forEach ab Java 8 verwenden:

 List<String> nameList   = new ArrayList<>(
            Arrays.asList("USA", "USSR", "UK"));

 nameList.forEach((v) -> System.out.println(v));
2
Sudip Bhandari

Für eine Rückwärtssuche sollten Sie Folgendes verwenden:

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

Wenn Sie eine Position kennen möchten, verwenden Sie iterator.previousIndex (). Es ist auch hilfreich, eine innere Schleife zu schreiben, die zwei Positionen in der Liste vergleicht (Iteratoren sind nicht gleich).

0
CoolMind

In Java 8 können Sie die List.forEach() -Methode mit lambda expression verwenden, um eine Liste zu durchlaufen.

import Java.util.ArrayList;
import Java.util.List;

public class TestA {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}
0