Frage Wie funktioniert die erweiterte for-Anweisung für Arrays und wie erhält man einen Iterator für ein Array?


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 das oben 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?

75
2017-10-12 08:25


Ursprung


Antworten:


Wenn du willst Iterator Über einem Array können Sie eine der direkten Implementierungen verwenden, anstatt das Array in ein Array zu schreiben List. Beispielsweise:

Apache Commons Sammlungen ArrayIterator

Oder, wenn Sie Generika verwenden möchten:

com.Ostermiller.util.ArrayIterator

Beachten Sie, dass wenn Sie eine haben möchten Iterator über primitive Typen können Sie nicht, weil ein primitiver Typ kein generischer Parameter sein kann. Zum Beispiel, wenn Sie ein Iterator<int>, du musst ein verwenden Iterator<Integer> stattdessen, was zu einer Menge von Autoboxing und -unboxing führen wird, wenn das von einem unterstützt wird int[].


55
2017-10-12 10:57



Nein, es gibt keine Konvertierung. Die JVM iteriert einfach über das Array mit einem Index im Hintergrund.

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

Beachten Sie, dass für die Verwendung keine Leistungseinbußen entstehen   die for-each-Schleife, auch für Arrays. In der Tat kann es einen leichten Leistungsvorteil bieten   über eine normale for-Schleife in einigen Fällen, wie es die Grenze von berechnet   der Array-Index nur einmal.

Du kannst also keine bekommen Iterator für ein Array (außer natürlich, indem man es in a umwandelt List zuerst).


49
2017-10-12 08:28



Arrays.asList (arr) .iterator ();

Oder schreiben Sie Ihre eigene, die ListIterator-Schnittstelle implementierend.


33
2017-10-12 08:32



Google Guaven Libraries Sammlung bietet eine solche Funktion:

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

Man sollte Guava über die Apache Collection vorziehen (die aufgegeben zu sein scheint).


31
2017-11-15 10:08



In Java 8:

Arrays.stream(arr).iterator();

14
2018-03-07 17:42



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();
  }
}

10
2017-07-09 09:25



Streng genommen können Sie keinen Iterator des primitiven Arrays erhalten, weil Iterator.next () kann nur ein Objekt zurückgeben. Aber durch die Magie des Autoboxings können Sie den Iterator mit Hilfe des Arrays.asList () Methode.

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


9
2017-10-12 08:36



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 Erator auf diese Liste setzen. Dafür muss Ihr Array ein sein Ganze Zahl Array (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 Iterator wie diesen erhalten, aber ich rate Ihnen davon ab. Performances sind nicht gut im Vergleich zu einer direkten Iteration auf dem Array mit dem "für Syntax erweitert".

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


5
2017-10-12 08:33



Wie funktioniert das oben für jede Schleife?

Wie viele andere Array-Features 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 Untertyp von ist Iterable, dann ist die Übersetzung wie folgt

[...]

Ansonsten hat der Ausdruck notwendigerweise einen Array-Typ, T[]. [[ ZAUBER! ]]

Lassen L1 ... Lm sei die (möglicherweise leere) Sequenz von Etiketten, die der erweiterten for-Anweisung unmittelbar vorangehen.

Die erweiterte for-Anweisung entspricht einer Basis für die Anweisung des Formulars:

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

#a und #i sind automatisch generierte Identifikatoren, die sich von anderen Identifikatoren (automatisch oder anderweitig) unterscheiden, die sich an dem Punkt befinden, an dem die erweiterte for-Anweisung auftritt.

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

Lasst uns javap es oben:

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 es einfacher zu lesen:

 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:

  • 0 zu 14: Erzeuge das Array
  • 15 zu 22: Bereite dich auf die for-Schleife vor. Beim 22, Ganzzahl speichern 0 vom Stapel in die lokale Position 4. Das ist die Schleifenvariable.
  • 24 zu 47: die Schleife. Die Schleifenvariable wird bei abgerufen 31und inkrementiert um 44. Wenn es gleich der Array-Länge ist, die in der lokalen Variablen 3 auf dem Scheck bei gespeichert wird 27, endet die Schleife.

Fazit: Es ist das gleiche wie eine explizite for-Schleife mit einer Indexvariablen, keine Itereators beteiligt.


4
2018-04-06 21:02



Für (2) bietet Guava genau das, was Sie wollen Int.asList (). Es gibt ein Äquivalent für jeden Grundtyp in der zugehörigen Klasse, z. Booleans zum boolean, etc.

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

3
2018-01-11 04:15



Ich bin ein wenig zu spät zum Spiel, aber mir sind einige wichtige Punkte aufgefallen, vor allem in Bezug auf Java 8 und die Effizienz von Arrays.asList.

1. Wie funktioniert die for-each-Schleife?

Wie Ciro Santilli 六四 事件 法轮功 包 轩 aufgezeigt, gibt es ein praktisches Dienstprogramm zur Untersuchung von Bytecode, der mit dem JDK ausgeliefert wird: javap. Damit können wir feststellen, dass die folgenden zwei Code-Snippets den gleichen Bytecode wie in Java 8u74 erzeugen:

Für jede Schleife:

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

Für 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?

Während dies nicht für Primitive funktioniert, sollte beachtet werden, dass ein Array in eine List mit konvertiert wird Arrays.asList wirkt sich nicht signifikant auf die Leistung aus. Der Einfluss auf Speicher und Leistung ist nahezu unermesslich.

Arrays.asList verwendet keine normale List-Implementierung, die als Klasse leicht zugänglich ist. Es benutzt java.util.Arrays.ArrayList, das ist nicht das Gleiche wie java.util.ArrayList. Es ist ein sehr dünner Wrapper um ein Array und kann nicht in der Größe verändert werden. Blick auf den Quellcode für java.util.Arrays.ArrayListkönnen wir sehen, dass es funktional äquivalent zu einem Array ist. Es gibt fast keinen Overhead. Beachten Sie, dass ich nur den relevantesten Code weggelassen habe und eigene 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. Soweit Iteratoren gehen, ist es sehr einfach; es ruft einfach an get() bis size() erreicht, ähnlich wie es ein Manual for Loop tun würde. Es ist die einfachste und meist effizienteste Implementierung eines Iterator für ein Array.

Nochmal, Arrays.asList erstellt kein a java.util.ArrayList. Es ist viel leichter und eignet sich für den Erhalt eines Iterators mit vernachlässigbarem Overhead.

Primitive Arrays

Wie andere bemerkt haben, Arrays.asList kann nicht für primitive Arrays verwendet werden. Java 8 stellt mehrere neue Technologien für den Umgang mit Datensammlungen vor, von denen einige dazu verwendet werden können, einfache und relativ effiziente Iteratoren aus Arrays zu extrahieren. Beachten Sie, dass Sie, wenn Sie Generika verwenden, immer das Boxing-Unboxing-Problem haben: Sie müssen von int zu Integer und dann zurück zu int konvertieren. Während Boxing / Unboxing normalerweise vernachlässigbar ist, hat es in diesem Fall einen O (1) Leistungseinfluss und könnte zu Problemen mit sehr großen Arrays oder auf Computern mit sehr begrenzten Ressourcen führen (d. H. SoC).

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

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

Die Stream-API bietet auch Konstrukte zur Vermeidung des Boxing-Problems an erster Stelle, aber dies erfordert den Verzicht auf Iteratoren zugunsten von Streams. 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 hinzu java.util.PrimitiveIterator. Dies bietet das Beste aus beiden Welten: Kompatibilität mit Iterator<T> durch Boxen zusammen mit Methoden, um Boxen zu vermeiden. PrimitiveIterator verfügt über drei integrierte Schnittstellen, die es erweitern: OfInt, OfLong und OfDouble. Alle drei werden boxen, wenn next() wird aufgerufen, kann aber auch Primitive über Methoden wie nextInt(). Neuerer Code für Java 8 sollte nicht verwendet werden next() es sei denn, Boxen ist absolut notwendig.

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 auf Java 8 sind, 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 umgehen, indem Sie Ihre eigenen Methoden zum Erhalten von Primitiven 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. Das bedeutet jedoch nicht, dass es in einer Liste enthalten ist, wenn Sie etwas Leichtes wie z Arrays.asList.


3
2018-03-15 08:15