Wie funktioniert die erweiterte for-Anweisung für Arrays und wie wird ein Iterator für ein Array abgerufen?


76

Gegeben das folgende Code-Snippet:

int[] arr = {1, 2, 3};
for (int i : arr)
    System.out.println(i);

Ich habe folgende Fragen:

  1. Wie funktioniert die obige Schleife für jede Schleife?
  2. Wie bekomme ich einen Iterator für ein Array in Java?
  3. Wird das Array in eine Liste konvertiert, um den Iterator zu erhalten?


Die obige Frage zur Leistung wurde geschlossen. Es wird hier beantwortet: stackoverflow.com/questions/256859/…
Orangle

Ich finde keine endgültige Antwort auf Punkt 1. Testet die für jede Schleife den Typ des Elements, über das iteriert wird, und verwendet eine reguläre for-Schleife unter der Abdeckung, wenn es sich um ein Array handelt? Oder erstellt es ein Iterable im laufenden Betrieb? Oder wird es in eine Liste konvertiert? Ich habe keine endgültige Antwort darauf gefunden.
Elisabeth

Antworten:


55

Wenn Sie ein IteratorOver-Array wünschen , können Sie eine der direkten Implementierungen verwenden, anstatt das Array in ein Array zu verpacken List. Zum Beispiel:

Apache Commons-Sammlungen ArrayIterator

Oder dieses, wenn Sie Generika verwenden möchten:

com.Ostermiller.util.ArrayIterator

Beachten Sie Iterator, dass dies nicht möglich ist , wenn Sie einen überprimitiven Typ haben möchten , da ein primitiver Typ kein generischer Parameter sein kann. Wenn Sie beispielsweise eine möchten Iterator<int>, müssen Sie Iterator<Integer>stattdessen eine verwenden, was zu viel Autoboxing und -unboxing führt, wenn dies durch eine unterstützt wird int[].


2
Zustimmen. Oder erstellen Sie Ihren eigenen ArrayIterator, wenn Sie weiterhin den primitiven Datentyp verwenden
möchten

Die Code-Vervollständigung zeigt keine Vorschläge für ArrayIt; Annahme aller Bibliotheken von Drittanbietern.
Mark Jeronimus

50

Nein, es erfolgt keine Konvertierung. Die JVM durchläuft das Array nur mithilfe eines Index im Hintergrund.

Zitat aus Effective Java 2nd Ed., Punkt 46:

Beachten Sie, dass die Verwendung der for-each-Schleife auch für Arrays keine Leistungseinbußen nach sich zieht. Tatsächlich kann es unter bestimmten Umständen einen leichten Leistungsvorteil gegenüber einer normalen for-Schleife bieten, da es die Grenze des Array-Index nur einmal berechnet.

Sie können also kein Iteratorfür ein Array erhalten (es sei denn, Sie konvertieren es in ein Listerstes).


2
Ja, +1 Es ist nur ein normaler for. @Emil, du kannst nicht.
st0le

Effektives Java ist falsch, wenn es sich um eine leere Sammlung handelt. In diesem Fall wird unnötigerweise ein Iterator erstellt. Dies führt zu einer Objektabwanderung. Das Update besteht darin, es in eine isEmpty () - Prüfung einzuschließen. Dies ist besonders wichtig bei Geräten mit eingeschränktem Speicher wie Android.
Tastatur

Sind wir sicher, dass keine Iterable für das Array erstellt oder das Array in eine Liste konvertiert wird? Mit anderen Worten, im Inneren sagt Java: "Wenn der Typ ein Array ist, verwenden Sie eine reguläre for-Schleife mit einem versteckten Index, andernfalls verwenden Sie die Iterable." ???
Elisabeth

33

Arrays.asList (arr) .iterator ();

Oder schreiben Sie Ihre eigene, implementierende ListIterator-Schnittstelle.


23
Arrays.asList()kann nicht eine int[], nur Object[]und Unterklassen nehmen.
ILMTitan

3
das ist falsch. dies gibt ein List<int[]>und damit ein Iterator<int[]>, kein a zurück List<Integer>!
NJZK2

Sie konvertieren das Array in eine Liste, nur um einen Iterator bereitzustellen ?!
Khaled.K

1
@KhaledKhnifer Es wird wirklich nicht viel konvertiert. Arrays.asList()Verwendet das eigentliche Array und umschließt es mit einer Klasse, die es an die Listenschnittstelle anpasst. Mit Ausnahme des Problems "Nicht unterstützende Grundelemente" (und für Personen, die in einer nicht primitiven Situation mit demselben Problem hierher gekommen sind) ist dies tatsächlich eine gute Lösung.
Jasper

@ Khaled.K, siehe die neue Antwort von Zenexer ( stackoverflow.com/users/1188377/zenexer ).
Orangle

31

Die Sammlung von Google Guava Librarie bietet folgende Funktionen:

Iterator<String> it = Iterators.forArray(array);

Man sollte Guave der Apache-Sammlung vorziehen (die aufgegeben zu sein scheint).


1
Dies ist normalerweise die beste Antwort, außer wenn es sich um eine Reihe von Grundelementen (wie z. B. int[]) handelt, dann funktioniert dies nicht mehr :( da wir nicht spezifizieren können Iterator<int>oderIterator<Integer>
chakrit


10
public class ArrayIterator<T> implements Iterator<T> {
  private T array[];
  private int pos = 0;

  public ArrayIterator(T anArray[]) {
    array = anArray;
  }

  public boolean hasNext() {
    return pos < array.length;
  }

  public T next() throws NoSuchElementException {
    if (hasNext())
      return array[pos++];
    else
      throw new NoSuchElementException();
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

9

Genau genommen können Sie keinen Iterator des primitiven Arrays erhalten, da Iterator.next () nur ein Objekt zurückgeben kann. Durch die Magie des Autoboxing können Sie den Iterator mithilfe der Arrays.asList () -Methode abrufen .

Iterator<Integer> it = Arrays.asList(arr).iterator();

Die obige Antwort ist falsch. Sie können sie nicht Arrays.asList()für ein primitives Array verwenden. Sie würde a zurückgeben List<int[]>. Verwenden Guava ‚s Ints.asList()statt.


5

Sie können keinen Iterator für ein Array direkt abrufen.

Sie können jedoch eine Liste verwenden, die von Ihrem Array unterstützt wird, und einen Iierator in diese Liste aufnehmen. Dazu muss Ihr Array ein Integer- Array sein (anstelle eines int-Arrays):

Integer[] arr={1,2,3};
List<Integer> arrAsList = Arrays.asList(arr);
Iterator<Integer> iter = arrAsList.iterator();

Hinweis: Es ist nur Theorie. Sie können einen solchen Iterator bekommen, aber ich rate Ihnen davon ab. Die Leistung ist im Vergleich zu einer direkten Iteration auf dem Array mit der "erweiterten Syntax" nicht gut.

Hinweis 2: Ein Listenkonstrukt mit dieser Methode unterstützt nicht alle Methoden (da die Liste von einem Array mit fester Größe unterstützt wird). Beispielsweise führt die Methode "remove" Ihres Iterators zu einer Ausnahme.


Ich glaube nicht, dass es funktionieren wird. Arrays.asList (arr) gibt eine Liste vom Typ int [] zurück, da es sich um ein primitives Array handelt.
Emil

@Emil: Java-Generika funktionieren nicht für primitive Typen. Arrays.asListkümmert sich implizit darum, indem die Werte im Array eingerahmt werden. Daher ist das Ergebnis wirklich ist ein List<Integer>.
Konrad Rudolph

@emil es funktioniert, weil Arrays.asList einen varargs-Parameter nimmt
Sean Patrick Floyd

@ Seanizer: aber ich habe den obigen Code in meiner IDE versucht und es zeigt Fehler.
Emil

es kompiliert in meiner Sonnenfinsternis. Haben Sie die Compiler-Konformität auf mindestens 1,5 festgelegt?
Sean Patrick Floyd

4

Wie funktioniert die obige Schleife für jede Schleife?

Wie viele andere Array-Funktionen erwähnt die JSL Arrays explizit und verleiht ihnen magische Eigenschaften. JLS 7 14.14.2 :

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

Wenn der Ausdruckstyp ein Subtyp von Iterableist, lautet die Übersetzung wie folgt

[...]

Andernfalls hat der Ausdruck notwendigerweise einen Array-Typ T[]. [[ MAGIE! ]]

Sei L1 ... Lmdie (möglicherweise leere) Folge von Beschriftungen unmittelbar vor der erweiterten for-Anweisung.

Die erweiterte for-Anweisung entspricht einer grundlegenden for-Anweisung des Formulars:

T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    VariableModifiersopt TargetType Identifier = #a[#i];
    Statement
}

#aund #isind automatisch generierte Bezeichner, die sich von allen anderen (automatisch generierten oder anderweitigen) Bezeichnern unterscheiden, die sich an dem Punkt im Geltungsbereich befinden, an dem die erweiterte for-Anweisung auftritt.

Wird das Array in eine Liste konvertiert, um den Iterator zu erhalten?

Lass es uns javaptun:

public class ArrayForLoop {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i : arr)
            System.out.println(i);
    }
}

dann:

javac ArrayForLoop.java
javap -v ArrayForLoop

main Methode mit ein wenig Bearbeitung, um das Lesen zu erleichtern:

 0: iconst_3
 1: newarray       int
 3: dup
 4: iconst_0
 5: iconst_1
 6: iastore
 7: dup
 8: iconst_1
 9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore

15: astore_1
16: aload_1
17: astore_2
18: aload_2
19: arraylength
20: istore_3
21: iconst_0
22: istore        4

24: iload         4
26: iload_3
27: if_icmpge     50
30: aload_2
31: iload         4
33: iaload
34: istore        5
36: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
39: iload         5
41: invokevirtual #3    // Method java/io/PrintStream.println:(I)V
44: iinc          4, 1
47: goto          24
50: return

Nervenzusammenbruch:

  • 0to 14: Erstellen Sie das Array
  • 15to 22: bereite dich auf die for-Schleife vor. Speichern Sie bei 22 die Ganzzahl 0vom Stapel in die lokale Position 4. DAS ist die Schleifenvariable.
  • 24zu 47: der Schleife. Die Schleifenvariable wird um abgerufen 31und um erhöht 44. Wenn es der Array-Länge entspricht, die bei der Prüfung in in der lokalen Variablen 3 gespeichert ist 27, endet die Schleife.

Schlussfolgerung : Dies entspricht einer expliziten for-Schleife mit einer Indexvariablen, an der keine Itereatoren beteiligt sind.


4

Ich bin etwas spät dran, aber ich habe einige wichtige Punkte bemerkt, die ausgelassen wurden, insbesondere in Bezug auf Java 8 und die Effizienz von Arrays.asList.

1. Wie funktioniert die for-each-Schleife?

Wie Ciro Santilli hervorhob , gibt es ein praktisches Dienstprogramm zum Überprüfen des Bytecodes, der mit dem JDK geliefert wird : javap. Auf diese Weise können wir feststellen, dass die folgenden zwei Codefragmente identischen Bytecode wie in Java 8u74 erzeugen:

Für jede Schleife:

int[] arr = {1, 2, 3};
for (int n : arr) {
    System.out.println(n);
}

For-Schleife:

int[] arr = {1, 2, 3};

{  // These extra braces are to limit scope; they do not affect the bytecode
    int[] iter = arr;
    int length = iter.length;
    for (int i = 0; i < length; i++) {
        int n = iter[i];
        System.out.println(n);
    }
}

2. Wie bekomme ich einen Iterator für ein Array in Java?

Dies funktioniert zwar nicht für Grundelemente, es sollte jedoch beachtet werden, dass das Konvertieren eines Arrays in eine Liste mit Arrays.asListkeine wesentlichen Auswirkungen auf die Leistung hat. Die Auswirkungen auf Speicher und Leistung sind nahezu unermesslich.

Arrays.asListverwendet keine normale List-Implementierung, auf die als Klasse leicht zugegriffen werden kann. Es verwendet java.util.Arrays.ArrayList, was nicht dasselbe ist wie java.util.ArrayList. Es ist ein sehr dünner Wrapper um ein Array und kann nicht in der Größe geändert werden. Wenn java.util.Arrays.ArrayListwir uns den Quellcode ansehen, sehen wir, dass er funktional einem Array entspricht. Es gibt fast keinen Overhead. Beachten Sie, dass ich alle bis auf den relevantesten Code weggelassen und meine eigenen Kommentare hinzugefügt habe.

public class Arrays {
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
    }
}

Der Iterator ist bei java.util.AbstractList.Itr. Was Iteratoren betrifft, ist es sehr einfach; Es wird nur aufgerufen, get()bis size()es erreicht ist, ähnlich wie es ein Handbuch für die Schleife tun würde. Es ist die einfachste und normalerweise effizienteste Implementierung eines Iteratorfür ein Array.

Auch Arrays.asListhier wird kein erstellt java.util.ArrayList. Es ist viel leichter und geeignet, um einen Iterator mit vernachlässigbarem Overhead zu erhalten.

Primitive Arrays

Wie andere angemerkt haben, Arrays.asListkann nicht für primitive Arrays verwendet werden. Java 8 führt mehrere neue Technologien für den Umgang mit Datensammlungen ein, von denen einige verwendet werden könnten, um einfache und relativ effiziente Iteratoren aus Arrays zu extrahieren. Beachten Sie, dass bei Verwendung von Generika immer das Problem des Boxens und Entpackens auftritt: Sie müssen von int nach Integer und dann zurück nach int konvertieren. Während das Boxen / Entpacken normalerweise vernachlässigbar ist, hat es in diesem Fall einen Einfluss auf die O (1) -Leistung und kann zu Problemen mit sehr großen Arrays oder auf Computern mit sehr begrenzten Ressourcen (z. B. SoC ) führen.

Mein persönlicher Favorit für jede Art von Array-Casting / Boxing-Operation in Java 8 ist die neue Stream-API. Zum Beispiel:

int[] arr = {1, 2, 3};
Iterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator();

Die Streams-API bietet auch Konstrukte, um das Boxproblem zu vermeiden. Dazu müssen jedoch Iteratoren zugunsten von Streams aufgegeben werden. Es gibt dedizierte Stream-Typen für int, long und double (IntStream, LongStream bzw. DoubleStream).

int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
stream.forEach(System.out::println);

Interessanterweise fügt Java 8 auch hinzu java.util.PrimitiveIterator. Dies bietet das Beste aus beiden Welten: Kompatibilität mit Iterator<T>Via-Boxen sowie Methoden zur Vermeidung von Boxen. PrimitiveIterator verfügt über drei integrierte Schnittstellen, die es erweitern: OfInt, OfLong und OfDouble. Alle drei werden boxen, wenn next()aufgerufen wird, können aber auch Primitive über Methoden wie zurückgeben nextInt(). Neuerer Code, der für Java 8 entwickelt wurde, sollte die Verwendung vermeiden, es next()sei denn, Boxen ist unbedingt erforderlich.

int[] arr = {1, 2, 3};
PrimitiveIterator.OfInt iterator = Arrays.stream(arr);

// You can use it as an Iterator<Integer> without casting:
Iterator<Integer> example = iterator;

// You can obtain primitives while iterating without ever boxing/unboxing:
while (iterator.hasNext()) {
    // Would result in boxing + unboxing:
    //int n = iterator.next();

    // No boxing/unboxing:
    int n = iterator.nextInt();

    System.out.println(n);
}

Wenn Sie noch nicht mit Java 8 arbeiten, ist Ihre einfachste Option leider viel weniger prägnant und wird mit ziemlicher Sicherheit das Boxen beinhalten:

final int[] arr = {1, 2, 3};
Iterator<Integer> iterator = new Iterator<Integer>() {
    int i = 0;

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
};

Oder wenn Sie etwas wiederverwendbareres erstellen möchten:

public final class IntIterator implements Iterator<Integer> {
    private final int[] arr;
    private int i = 0;

    public IntIterator(int[] arr) {
        this.arr = arr;
    }

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
}

Sie könnten das Boxproblem hier umgehen, indem Sie Ihre eigenen Methoden zum Abrufen von Grundelementen hinzufügen, aber es würde nur mit Ihrem eigenen internen Code funktionieren.

3. Wird das Array in eine Liste konvertiert, um den Iterator zu erhalten?

Nein ist es nicht. Dies bedeutet jedoch nicht, dass das Einbinden in eine Liste zu einer schlechteren Leistung führt, vorausgesetzt, Sie verwenden etwas Leichtes wie Arrays.asList.


1
Die vollständigste Antwort meiner Meinung nach, aber ich denke, Sie sollten ersetzen mapToObj(Integer::new)durchmapToObj(Integer::valueOf)
Michael Schmeißer

Es scheint, Sie haben Recht , @ MichaelSchmeißer. Danke für den Tipp. Ich habe die Antwort entsprechend aktualisiert.
Zenexer

3

Für (2) bietet Guava genau das, was Sie als Int.asList () wollen . Für jeden primitiven Typ in der zugehörigen Klasse gibt es ein Äquivalent, z. B. Booleansfür booleanusw.

    int[] arr={1,2,3};
    for(Integer i : Ints.asList(arr)) {
      System.out.println(i);
    }

2

Ich bin ein neuer Student, aber ich glaube, dass das ursprüngliche Beispiel mit int [] über das Primitiv-Array iteriert, aber nicht mithilfe eines Iterator-Objekts. Es hat lediglich die gleiche (ähnliche) Syntax mit unterschiedlichen Inhalten,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList () wendet anscheinend nur List-Methoden auf ein Objektarray an, das angegeben ist - für jede andere Art von Objekt, einschließlich eines primitiven Arrays, iterator (). Next () gibt Ihnen anscheinend nur den Verweis auf das ursprüngliche Objekt und behandelt ihn es als Liste mit einem Element. Können wir den Quellcode dafür sehen? Würdest du nicht eine Ausnahme bevorzugen? Keine Ursache. Ich denke (das ist GUESS), dass es wie eine Singleton-Sammlung ist (oder IST). Hier ist asList () für den Fall mit einem primitiven Array irrelevant, aber verwirrend. Ich weiß nicht, ob ich Recht habe, aber ich habe ein Programm geschrieben, das besagt, dass ich es bin.

Daher dieses Beispiel (in dem asList () im Grunde nicht das tut, was Sie erwartet hatten, und daher nicht etwas ist, das Sie tatsächlich auf diese Weise verwenden würden) - Ich hoffe, der Code funktioniert besser als meine Kennzeichnung als Code, und Hey, sieh dir diese letzte Zeile an:

// Java(TM) SE Runtime Environment (build 1.6.0_19-b04)

import java.util.*;

public class Page0434Ex00Ver07 {
public static void main(String[] args) {
    int[] ii = new int[4];
    ii[0] = 2;
    ii[1] = 3;
    ii[2] = 5;
    ii[3] = 7;

    Arrays.asList(ii);

    Iterator ai = Arrays.asList(ii).iterator();

    int[] i2 = (int[]) ai.next();

    for (int i : i2) {
        System.out.println(i);
    }

    System.out.println(Arrays.asList(12345678).iterator().next());
}
}

1

Ich mag die Antwort vom 30. mit IteratorsGuave. Von einigen Frameworks erhalte ich jedoch null anstelle eines leeren Arrays und kann Iterators.forArray(array)damit nicht gut umgehen. Also habe ich mir diese Hilfsmethode ausgedacht, mit der Sie aufrufen könnenIterator<String> it = emptyIfNull(array);

public static <F> UnmodifiableIterator<F> emptyIfNull(F[] array) {
    if (array != null) {
        return Iterators.forArray(array);
    }
    return new UnmodifiableIterator<F>() {
        public boolean hasNext() {
            return false;
        }

        public F next() {
            return null;
        }
    };
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.