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};
{
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.asList
keine wesentlichen Auswirkungen auf die Leistung hat. Die Auswirkungen auf Speicher und Leistung sind nahezu unermesslich.
Arrays.asList
verwendet 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.ArrayList
wir 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 Iterator
für ein Array.
Auch Arrays.asList
hier 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.asList
kann 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);
Iterator<Integer> example = iterator;
while (iterator.hasNext()) {
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
.