Warum kann ein Array nicht Iterable zugewiesen werden?

186

Mit Java5 können wir schreiben:

Foo[] foos = ...
for (Foo foo : foos) 

oder einfach ein Iterable in der for-Schleife verwenden. Das ist sehr praktisch.

Sie können jedoch keine generische Methode für iterable wie folgt schreiben:

public void bar(Iterable<Foo> foos) { .. }

und es mit einem Array aufrufen, da es kein Iterable ist:

Foo[] foos = { .. };
bar(foos);  // compile time error 

Ich frage mich über die Gründe für diese Designentscheidung.

dfa
quelle
8
Arrays.asList ist wohl gut genug
dfa
17
es ist eine philosophische Frage
dfa
2
Ein guter Grund, sich mit Arrays in Java 5+ zu befassen, sind varargs-Methoden.
Jeff Walker
2
@Torsten: stimmt, aber wenn Sie es an eine Methode übergeben, die eine Iterable akzeptiert, werden Sie wahrscheinlich sowieso keine Änderungen vornehmen.
Michael Myers
5
Tatsächlich ist Arrays.asList nicht gut genug, da es nicht für Arrays primitiver Typen funktioniert. Die einzige integrierte Möglichkeit, Elemente primitiver Typen generisch (in Kästchen) zu iterieren, besteht in der Verwendung von Reflexion, wobei java.lang.reflect.Arrayjedoch die Leistung schwach ist. Sie können jedoch Ihre eigenen Iteratoren (oder Listenimplementierungen!) Schreiben, um Arrays primitiver Typen zu verpacken, wenn Sie möchten.
Boann

Antworten:

78

Arrays können Schnittstellen ( Cloneableund java.io.Serializable) implementieren . Warum also nicht Iterable? Ich denke, Iterableerzwingt das Hinzufügen einer iteratorMethode, und Arrays implementieren keine Methoden. char[]überschreibt nicht einmal toString. Auf jeden Fall sollten Referenzarrays als weniger ideal angesehen werden - verwenden Sie Lists. Wie dfa kommentiert, Arrays.asListwird die Konvertierung explizit für Sie durchgeführt.

(Allerdings können Sie cloneArrays aufrufen .)

Tom Hawtin - Tackline
quelle
23
> "... und Arrays implementieren keine Methoden." Ich denke, das ist eine andere philosophische Frage; Arrays waren nie primitive Typen, und die Java-Philosophie lautet: "Alles ist ein Objekt (außer primitiven Typen)". Warum implementieren Arrays dann keine Methoden, obwohl es eine Unmenge von Operationen gibt, für die man von Anfang an ein Array verwenden möchte? Oh, das stimmt, Arrays waren die einzigen stark typisierten Sammlungen, bevor Generika im Nachhinein auftauchten.
Fatuhoku
2
Wenn Sie Daten in einem Array haben, ist es möglich, dass Sie leistungskritische Arbeiten auf niedriger Ebene ausführen, z. B. das Lesen von Bytes [] aus Streams. Die Unfähigkeit, Arrays zu iterieren, beruht wahrscheinlich auf Java-Generika, die keine Grundelemente als Typargumente unterstützen, wie @Gareth weiter unten sagt.
Drew Noakes
2
@FatuHoku Es ist falsch, Generika im Nachhinein vorzuschlagen. Die Wünschbarkeit von Generika wurde immer geschätzt. Arrays sind keine Grundelemente (ich habe nicht gesagt, dass sie es sind), aber sie sind niedrig. Das einzige, was Sie mit Arrays tun möchten, ist, sie als Implementierungsdetail für vektorähnliche Strukturen zu verwenden.
Tom Hawtin - Tackline
1
Iterator<T>erfordert auch remove(T), obwohl es erlaubt ist ein zu werfen UnsupportedOperationException.
Wchargin
Arrays implementieren Methoden: Sie implementieren alle Methoden von java.lang.Object.
Mhsmith
59

Das Array ist ein Objekt, seine Elemente jedoch möglicherweise nicht. Das Array enthält möglicherweise einen primitiven Typ wie int, mit dem Iterable nicht umgehen kann. Zumindest denke ich das.

Gareth Adamson
quelle
3
Dies bedeutet, dass zur Unterstützung der IterableSchnittstelle primitive Arrays auf die Verwendung der Wrapper-Klassen spezialisiert sein müssen. Nichts davon ist wirklich eine große Sache, da die Typparameter sowieso alle falsch sind.
Thejoshwolfe
8
Dies würde Objektarrays nicht daran hindern, Iterable zu implementieren. Es würde auch nicht verhindern, dass primitive Arrays Iterable für den umschlossenen Typ implementieren.
Boann
1
Autoboxing könnte damit umgehen
Tim Büthe
Ich denke das ist der richtige Grund. Es funktioniert nicht zufriedenstellend für Arrays primitiver Typen, bis Generika primitive Typen unterstützen (z. B. List<int>anstelle von List<Integer>usw.). Ein Hack könnte mit Wrappern durchgeführt werden, aber mit einem Leistungsverlust - und was noch wichtiger ist - würde dieser Hack in Zukunft verhindern, dass er ordnungsgemäß in Java implementiert wird (zum Beispiel int[].iterator()wäre er für immer gesperrt, um zurückzukehren, Iterator<Integer>anstatt Iterator<int>). Vielleicht werden Arrays durch die bevorstehenden Werttypen + generische Spezialisierung für Java (Projekt valhalla) implementiert Iterable.
Bjarke
16

Arrays sollten dies unterstützen Iterable, sie tun dies jedoch nicht, aus dem gleichen Grund, aus dem .NET-Arrays keine Schnittstelle unterstützen, die einen schreibgeschützten Direktzugriff nach Position ermöglicht (es gibt keine solche als Standard definierte Schnittstelle). Grundsätzlich weisen Frameworks oft störende kleine Lücken auf, deren Behebung niemandes Zeit wert ist. Es wäre egal, ob wir sie selbst optimal reparieren könnten, aber oft können wir es nicht.

UPDATE: Um ausgeglichen zu sein, habe ich .NET-Arrays erwähnt, die keine Schnittstelle unterstützen, die den wahlfreien Zugriff nach Position unterstützt (siehe auch meinen Kommentar). In .NET 4.5 wurde diese genaue Schnittstelle definiert und wird von Arrays und der List<T>Klasse unterstützt:

IReadOnlyList<int> a = new[] {1, 2, 3, 4};
IReadOnlyList<int> b = new List<int> { 1, 2, 3, 4 };

Es ist immer noch nicht alles perfekt, da die veränderbare Listenschnittstelle IList<T>nicht erbtIReadOnlyList<T> :

IList<int> c = new List<int> { 1, 2, 3, 4 };
IReadOnlyList<int> d = c; // error

Vielleicht gibt es eine mögliche Abwärtskompatibilität mit einer solchen Änderung.

Wenn es in neueren Versionen von Java Fortschritte bei ähnlichen Dingen gibt, würde ich mich über die Kommentare freuen! :) :)

Daniel Earwicker
quelle
8
.NET Arrays implementieren die IList-Schnittstelle
Tom Gillen
2
@Aphid - Ich sagte schreibgeschützten Direktzugriff. IList<T>macht Operationen zum Ändern verfügbar. Es wäre großartig, wenn Sie so IList<T>etwas wie eine IReadonlyList<T>Schnittstelle geerbt hätten, die gerade Countund T this[int]und geerbt hätte IEnumerable<T>(die bereits schreibgeschützte Aufzählung unterstützt). Eine weitere großartige Sache wäre eine Schnittstelle zum Abrufen eines Enumerators in umgekehrter Reihenfolge, nach der die ReverseErweiterungsmethode abfragen könnte (genau wie die CountErweiterungsmethode ICollection
nachfragen muss
Ja, es wäre viel besser, wenn die Dinge so gestaltet worden wären. Die IList-Schnittstelle definiert IsReadOnly- und IsFixedSize-Eigenschaften, die vom Array entsprechend implementiert werden. Das hat mich jedoch immer als sehr schlecht empfunden, da es keine Kompilierungszeit bietet, um zu überprüfen, ob die von Ihnen angegebene Liste tatsächlich schreibgeschützt ist, und ich sehe sehr selten Code, der diese Eigenschaften überprüft.
Tom Gillen
1
Arrays in .NET implementieren IList& ICollectionseit .NET 1.1 IList<T>und ICollection<T>seit .NET 2.0. Dies ist ein weiterer Fall, in dem Java weit hinter der Konkurrenz zurückliegt.
Amir Abiri
@ TomGillen: Mein größtes Problem IListist, dass es keine abfragbareren Attribute bietet. Ich würde sagen, ein geeigneter Satz sollte für den Anfang IsUpdateable, IsResizable, IsReadOnly, IsFixedSize und ExistingElementsAreImmutable enthalten. Die Frage, ob Code eine Referenz ohne Typumwandlung eine Liste ändern kann, unterscheidet sich von der Frage, ob Code, der eine Referenz auf eine Liste enthält, die nicht geändert werden soll, diese Referenz sicher direkt mit externem Code teilen kann oder ob Es kann davon ausgegangen werden, dass sich ein Aspekt der Liste niemals ändern wird.
Supercat
14

Leider sind Arrays nicht classgenug. Sie implementieren die IterableSchnittstelle nicht.

Während Arrays jetzt Objekte sind, die Clonable und Serializable implementieren, glaube ich, dass ein Array kein Objekt im normalen Sinne ist und die Schnittstelle nicht implementiert.

Der Grund, warum Sie sie in for-each-Schleifen verwenden können, ist, dass Sun etwas synthetischen Zucker für Arrays hinzugefügt hat (dies ist ein Sonderfall).

Da Arrays mit Java 1 als "fast Objekte" begannen, wäre eine Änderung viel zu drastisch, um sie zu echten Objekten in Java zu machen .

jjnguy
quelle
14
Trotzdem gibt es Zucker für jede Schleife. Warum kann es keinen Zucker für Iterable geben?
Michael Myers
8
@mmyers: Der Zucker, der für jeden verwendet wird , ist Zucker zur Kompilierungszeit . Das ist viel einfacher als VM- Zucker. Allerdings sind .NET-Arrays in diesem Bereich deutlich besser ...
Jon Skeet
12
Arrays können Schnittstellen implementieren. Sie implementieren Cloneableund SerializableSchnittstellen.
Notnoop
34
Java-Arrays sind Objekte in jeder Hinsicht. Bitte entfernen Sie diese Fehlinformationen. Sie implementieren Iterable einfach nicht.
Ykaganovich
5
Ein Array ist ein Objekt. Es unterstützt nützliche : P-Methoden wie wait (), wait (n), wait (n, m), notify (), notifyAll (), finalize (), eine sinnlose Implementierung von toString () Die einzige nützliche Methode ist getClass () .
Peter Lawrey
1

Der Compiler übersetzt das for eachauf einem Array tatsächlich in ein einfachesfor Schleife mit einer Zählervariablen.

Folgendes kompilieren

public void doArrayForEach() {
    int[] ints = new int[5];

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

und dann Dekompilieren der .class-Datei ergibt

public void doArrayForEach() {
    int[] ints = new int[5];
    int[] var2 = ints;
    int var3 = ints.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        int i = var2[var4];
        System.out.println(i);
    }
}
das Keks
quelle