Warum wird Enumeration in ArrayList und nicht in List in java.utils konvertiert?

74

Gibt es einen guten Grund, warum die Collections.list () -Methode im java.utilsPaket ein ArrayList<T>anstelle von zurückgibt List<T>?

Natürlich ArrayListist ein a List, aber ich habe den Eindruck, dass es im Allgemeinen eine gute Praxis ist, den Schnittstellentyp anstelle des Implementierungstyps zurückzugeben.

Tianxiang Xiong
quelle
Aus dem Javadoc: " Gibt eine Array-Liste zurück [...] ". Warum sie beschlossen haben, die API zu sperren, um eine zurückzugeben, ArrayListist unbekannt, und wir können diese Frage unmöglich beantworten, selbst wenn wir uns den Quellcode von ansehen Collections.
Laf
könnte daran liegen, dass die Reihenfolge der Aufzählungen eingehalten werden muss? Es wird in dem Link in Ihrer Frage erwähnt. Gibt eine Array-Liste zurück, die die von der angegebenen Aufzählung zurückgegebenen Elemente in der Reihenfolge enthält, in der sie von der Aufzählung zurückgegeben werden
Amit.rk3
5
@ Amit.rk3 Eine Liste behält immer die Reihenfolge, egal welche Implementierung Sie wählen.
Jib'z
@ Amit.rk3 A behält Listdie Reihenfolge bei, in der die Elemente hinzugefügt werden, oder zumindest ist dies der allgemeine Vertrag, der von der ListSchnittstelle definiert wird : " Eine geordnete Sammlung (auch als Sequenz bezeichnet) ".
Laf
@ Amit.rk3 Beachten Sie auch, dass sich die Aufzählung (und die Aufzählung, Kleinbuchstaben "e", die sie darstellt) von einer unterscheidet enum. Sie teilen einen Namen, aber sonst nichts.
Yshavit

Antworten:

54

Haftungsausschluss: Ich bin kein JDK-Autor.

Ich bin damit einverstanden, dass es richtig ist, eigenen Code in Schnittstellen zu schreiben. Wenn Sie jedoch eine veränderbare Sammlung an einen Drittanbieter zurückgeben, ist es wichtig, den Drittanbieter darüber zu informieren, welche Art von CodeList er zurückerhält.

LinkedListund ArrayListsind in Bezug auf die Leistung für verschiedene Operationen sehr unterschiedlich. Zum Beispiel kann das Entfernen des ersten Element eines ArrayListist O(n), aber das Entfernen des ersten Elements a LinkedListist O(1).

Durch die vollständige Angabe des Rückgabetyps übermitteln die JDK-Autoren in eindeutigem Code zusätzliche Informationen darüber , welche Art von Objekt sie Ihnen zurückgeben, sodass Sie Ihren Code schreiben können, um diese Methode ordnungsgemäß zu verwenden. Wenn Sie wirklich eine benötigen LinkedList, wissen Sie, dass Sie hier eine angeben müssen.

Der Hauptgrund für das Codieren einer Schnittstelle über eine Implementierung ist, wenn Sie glauben, dass sich die Implementierung ändern wird. Die JDK-Autoren gehen wahrscheinlich davon aus, dass sie diese Methode niemals ändern werden. es wird niemals ein LinkedListoder ein zurückgeben Collections.UnmodifiableList. In den meisten Fällen würden Sie jedoch wahrscheinlich immer noch Folgendes tun:

List<T> list = Collections.list(enumeration);
durron597
quelle
19
Es scheint, dass dies eine Art Postel'sches Gesetz für das API-Design ist: Seien Sie allgemein mit Ihren Eingaben, seien Sie spezifisch mit Ihren Ausgaben.
Tianxiang Xiong
2
Eines Tages werden sie eine toLinkedListMethode hinzufügen , und dann wird der Name listfür die ArrayList-Version albern aussehen.
user253751
1
Ich denke, die wichtige Information hier ist, ob die Liste veränderlich ist oder nicht. ZB welche Methoden der Schnittstelle eine UnsupportedOperationException auslösen und welche nicht.
KeuleJ
32

Wenn Sie zurückkehren List, werden Sie das Programm auf eine Schnittstelle hochstufen , was eine sehr gute Praxis ist. Dieser Ansatz hat jedoch seine Grenzen. Beispielsweise können Sie einige Methoden nicht verwenden, die für ArrayListdie ListSchnittstelle definiert sind und in dieser nicht vorhanden sind. Weitere Informationen finden Sie in dieser Antwort .

Ich zitiere das API-Design aus den Java ™ -Tutorials:

... Es ist in Ordnung, ein Objekt eines beliebigen Typs zurückzugeben , der eine der Erfassungsschnittstellen implementiert oder erweitert . Dies kann eine der Schnittstellen oder ein Spezialtyp sein, der eine dieser Schnittstellen erweitert oder implementiert.

In gewisser Hinsicht sollten Rückgabewerte das entgegengesetzte Verhalten von Eingabeparametern aufweisen: Es ist am besten , die spezifischste anwendbare Erfassungsschnittstelle und nicht die allgemeinste zurückzugeben . Wenn Sie beispielsweise sicher sind, dass Sie immer a zurückgeben SortedMap, sollten Sie der entsprechenden Methode den Rückgabetyp SortedMapanstatt geben Map. SortedMapDas Erstellen von MapInstanzen ist zeitaufwändiger als bei normalen Instanzen und außerdem leistungsfähiger. Da Ihr Modul bereits Zeit für die Erstellung eines Moduls investiert hat SortedMap, ist es sinnvoll, dem Benutzer Zugriff auf seine erhöhte Leistung zu gewähren. Darüber hinaus kann der Benutzer das zurückgegebene Objekt an Methoden übergeben, die a erfordern SortedMap, sowie an Methoden , die alle akzeptieren Map.

Da ArrayListes sich im Wesentlichen um ein Array handelt, sind sie meine erste Wahl, wenn ich ein "Sammlungsarray" benötige. Wenn ich also die Aufzählung in eine Liste konvertieren möchte, würde ich eine Array-Liste wählen.

In allen anderen Fällen ist es weiterhin gültig zu schreiben:

List<T> list = Collections.list(e);
Maroun
quelle
4
In Bezug auf "die spezifischste anwendbare Erfassungsschnittstelle anstelle der allgemeinsten" ist dies sinnvoll, aber eine ArrayList ist keine Schnittstelle. In diesem Fall scheint List die spezifischste Schnittstelle zu sein.
Tianxiang Xiong
2
Im Fall von a SortedMapfügt es einem normalen Benutzer Funktionalität hinzu Map, daher halte ich es für sinnvoll, ihn als Rückgabetyp anzugeben. A ArrayListfügt jedoch nichts zu a hinzu Listund könnte gegen a ausgetauscht werden, LinkedListund Sie würden nichts anderes sehen (in Bezug auf Funktionalität, nicht in Bezug auf Leistung). Trotzdem finde ich Ihre Referenz sehr interessant. +1.
Laf
1
@Laf gibt es eigentlich viele Dinge zu beachten bei der Auswahl LinkedListoder ArrayList. Als würde man Zeitkomplexität bekommen ..
Maroun
5
Um dies zu betonen: Dies sollte eine Entwurfsentscheidung sein und kein "Zufall". Wenn Sie Ihre Daten intern in eine Speicherung sind SortedMap, sollten Sie nicht darauf zurückkommen „ weil es ist ein“. Sie sollten entscheiden, ob der Anrufer WISSEN soll, dass dies ein ist SortedMap. Und Sie sollten sich bewusst sein, dass Sie, sobald Sie a zurückgeben SortedMap, es nie wieder auf ein gewöhnliches verallgemeinern können Map- während die andere Richtung (spezifischer werden und ein SortedMapwo zurückgeben, wo ursprünglich nur ein Mapzurückgegeben wurde) immer möglich ist.
Marco13
6

Funktionen, die "exklusives Eigentum" an neu erstellten veränderlichen Objekten zurückgeben, sollten häufig der spezifischste praktische Typ sein. Diejenigen, die unveränderliche Objekte zurückgeben, insbesondere wenn sie gemeinsam genutzt werden, sollten häufig weniger spezifische Typen zurückgeben.

Der Grund für die Unterscheidung ist, dass im ersteren Fall ein Objekt immer in der Lage sein wird, ein neues Objekt des angegebenen Typs zu erzeugen, und da der Empfänger das Objekt besitzt und nicht bekannt ist, welche Aktionen der Empfänger dort ausführen möchte Im Allgemeinen kann der Code, der das Objekt zurückgibt, nicht wissen, ob alternative Schnittstellenimplementierungen die Anforderungen des Empfängers erfüllen können.

Im letzteren Fall bedeutet die Tatsache, dass das Objekt unveränderlich ist, dass die Funktion möglicherweise einen alternativen Typ identifizieren kann, der alles kann, was ein komplizierterer Typ aufgrund seines genauen Inhalts tun könnte . Beispielsweise kann eine Immutable2dMatrixSchnittstelle von einer ImmutableArrayBacked2dMatrixKlasse und einer ImmutableDiagonal2dMatrixKlasse implementiert werden . Eine Funktion, die ein Quadrat Immutable2dMatrixzurückgeben soll, könnte entscheiden, eine ImmutableDiagonalMatrixInstanz zurückzugeben, wenn alle Elemente außerhalb der Hauptdiagonale Null sind, oder ImmutableArrayBackedMatrixwenn nicht. Der erstere Typ würde viel weniger Speicherplatz beanspruchen, aber der Empfänger sollte sich nicht um den Unterschied zwischen ihnen kümmern.

Durch die Rückgabe Immutable2dMatrixanstelle eines konkreten ImmutableArrayBackedMatrixCodes kann der Code den Rückgabetyp basierend auf dem Inhalt des Arrays auswählen. Dies bedeutet auch, dass, wenn der Code, der das Array zurückgeben soll, eine geeignete Implementierung enthält Immutable2dMatrix, dies einfach zurückgegeben werden kann, anstatt eine neue Instanz erstellen zu müssen . Diese beiden Faktoren können bei der Arbeit mit unveränderlichen Objekten zu großen "Gewinnen" führen.

Bei der Arbeit mit veränderlichen Objekten kommt jedoch keiner der beiden Faktoren ins Spiel. Die Tatsache, dass ein veränderliches Array beim Generieren möglicherweise keine Elemente außerhalb der Hauptdiagonale enthält, bedeutet nicht, dass es niemals solche Elemente enthält. Während a ImmutableDiagonalMatrixeffektiv ein Subtyp von Immutable2dMatrixa MutableDiagonalMatrixist , ist a folglich kein Subtyp von a Mutable2dMatrix, da das letztere Speicher außerhalb der Hauptdiagonale akzeptieren könnte, während das erstere dies nicht kann. Während unveränderliche Objekte häufig gemeinsam genutzt werden können und sollten, können veränderbare Objekte dies im Allgemeinen nicht. Eine Funktion, die nach einer neuen veränderlichen Sammlung gefragt wird, die mit bestimmten Inhalten initialisiert wurde, muss eine neue Sammlung erstellen, unabhängig davon, ob ihr Sicherungsspeicher dem angeforderten Typ entspricht oder nicht.

Superkatze
quelle
1

Das Aufrufen von Methoden über eine Schnittstelle und nicht direkt über ein Objekt ist mit einem geringen Aufwand verbunden.

Dieser Overhead beträgt häufig nicht mehr als 1 oder 2 Prozessoranweisungen. Der Aufwand für den Aufruf einer Methode ist noch geringer, wenn die JIT weiß, dass die Methode endgültig ist. Dies ist für den meisten Code, den Sie und ich richtig haben, nicht messbar, aber für die Low-Level-Methoden in java.utils kann in einigen Codes verwendet werden, bei denen es sich um ein Problem handelt.

Wie bereits in anderen Antworten erwähnt, wirkt sich der konkrete Typ des zurückgegebenen Objekts (auch wenn es hinter einer Schnittstelle verborgen ist) auf die Leistung des Codes aus, der es verwendet. Diese Leistungsänderung kann sehr groß sein, so dass die aufrufende Software nicht funktioniert.

Offensichtlich haben die Autoren von java.utilskeine Möglichkeit zu wissen, was die gesamte Software, die Collections.list () aufruft, mit dem Ergebnis macht, und keine Möglichkeit, diese Software erneut zu testen, wenn sie die Implantation von Collections.list () ändern. Daher werden sie die Implantation von Collections.list () nicht ändern, um einen anderen Listentyp zurückzugeben, selbst wenn das Typsystem dies zulässt!

Wenn Sie Ihre eigene Software schreiben, haben Sie (hoffentlich) einen automatisierten Test, der Ihren gesamten Code abdeckt, und ein gutes Verständnis dafür, wie Ihre Code-Wechselbeziehungen beinhalten, wissen, wo die Leistung ein Problem darstellt. Die Möglichkeit, eine Methode zu ändern, ohne die Anrufer ändern zu müssen, ist von großem Wert, während sich das Design der Software ändert.

Daher sind die beiden Kompromisse sehr unterschiedlich.

Ian Ringrose
quelle