(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Ich verstehe, dass mit (1) Implementierungen der List Schnittstelle ausgetauscht werden können. Es scheint, dass (1) normalerweise in einer Anwendung verwendet wird, unabhängig von der Notwendigkeit (ich selbst benutze dies immer).
Ich frage mich, ob jemand (2) verwendet?
Außerdem, wie oft (und kann ich bitte ein Beispiel bekommen) erfordert die Situation tatsächlich die Verwendung von (1) über (2) (dh wo (2) nicht ausreichen würde ... neben Kodierung an Schnittstellen und Best Practices usw.)
Fast immer wird der erste dem zweiten vorgezogen. Das erste hat den Vorteil, dass sich die Implementierung von List
ändern kann (beispielsweise in LinkedList
), ohne den Rest des Codes zu beeinflussen. Dies ist eine schwierige Aufgabe für einen ArrayList
, nicht nur, weil Sie ArrayList
überall in LinkedList
ändern müssen, sondern auch, weil Sie möglicherweise ArrayList
spezifische Methoden verwendet haben.
Sie können List
Implementierungen lesen hier . Sie können mit einem ArrayList
beginnen, stellen jedoch bald danach fest, dass eine andere Implementierung angemessener ist.
Ich frage mich, ob jemand (2) verwendet?
Ja. Aber selten aus gutem Grund (IMO).
Und die Leute werden verbrannt, weil sie ArrayList
verwendet haben, wenn sie List
hätten verwenden sollen:
Dienstprogrammmethoden wie Collections.singletonList(...)
oder Arrays.asList(...)
geben keinen ArrayList
zurück.
Methoden in der API List
garantieren nicht, dass eine Liste desselben Typs zurückgegeben wird.
Beispiel: Wenn jemand in https://stackoverflow.com/a/1481123/139985 verbrannt wurde, hatte das Poster Probleme beim "Schneiden", weil ArrayList.sublist(...)
keinen ArrayList
zurückgibt und er hatte entwarf seinen Code, um ArrayList
als Typ aller seiner Listenvariablen zu verwenden. Er "löste" das Problem, indem er die Unterliste in einen neuen ArrayList
kopierte.
Das Argument, dass Sie wissen müssen, wie sich List
verhält, wird weitgehend über die Markierungsschnittstelle RandomAccess
angesprochen. Ja, es ist ein bisschen klobig, aber die Alternative ist schlimmer.
Außerdem, wie oft erfordert die Situation tatsächlich die Verwendung von (1) statt (2) (d. H. Wo (2) nicht ausreichen würde, abgesehen von "Codierung für Schnittstellen" und Best Practices usw.).
Der "wie oft" Teil der Frage ist objektiv nicht zu beantworten.
(und kann ich bitte ein Beispiel bekommen)
Gelegentlich erfordert die Anwendung möglicherweise, dass Sie Methoden in der API ArrayList
verwenden, die nicht in der API List
sind. Zum Beispiel ensureCapacity(int)
, trimToSize()
oder removeRange(int, int)
. (Und der letzte tritt nur auf, wenn Sie einen Subtyp von ArrayList erstellt haben, der die Methode als public
deklariert.)
Dies ist der einzige vernünftige Grund für die Codierung für die Klasse und nicht für die Schnittstelle IMO.
(Theoretisch ist es möglich, dass sich die Leistung auf einigen Plattformen geringfügig verbessert ... unter bestimmten Umständen ... aber es lohnt sich nicht, dies zu tun, es sei denn, Sie benötigen wirklich die letzten 0,05%. Dies ist keine triftiger Grund, IMO.)
Sie können keinen effizienten Code schreiben, wenn Sie nicht wissen, ob der Direktzugriff effizient ist oder nicht.
Das ist ein gültiger Punkt. Allerdings bietet Java bessere Möglichkeiten, damit umzugehen. z.B.
public <T extends List & RandomAccess> void test(T list) {
// do stuff
}
Wenn Sie das mit einer Liste aufrufen, die RandomAccess
nicht implementiert, erhalten Sie einen Kompilierungsfehler.
Sie können auch dynamisch testen ... mit instanceof
... ob die statische Eingabe zu umständlich ist. Sie können sogar Ihren Code schreiben, um verschiedene Algorithmen (dynamisch) zu verwenden, je nachdem, ob eine Liste Direktzugriff unterstützt.
Beachten Sie, dass ArrayList
nicht die einzige Listenklasse ist, die RandomAccess
implementiert. Andere umfassen CopyOnWriteList
, Stack
und Vector
.
Ich habe gesehen, dass Leute das gleiche Argument über Serializable
vorbrachten (weil List
es nicht implementiert) ... aber der obige Ansatz löst auch dieses Problem. (Soweit es überhaupt lösbar ist mit Laufzeittypen. Ein ArrayList
schlägt die Serialisierung fehl, wenn ein Element nicht serialisierbar ist.)
Sie könnten beispielsweise entscheiden, dass LinkedList
die beste Wahl für Ihre Anwendung ist, aber später entscheiden, dass ArrayList
aus Gründen der Leistung eine bessere Wahl ist.
Verwenden:
List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
Anstatt von:
ArrayList list = new ArrayList();
als Referenz:
(hauptsächlich für das Sammlungsdiagramm)
Es wird als guter Stil angesehen , eine Referenz auf ein HashSet
oder TreeSet
in einer Variablen zu speichern vom Typ Set.
Set<String> names = new HashSet<String>();
Auf diese Weise müssen Sie nur eine Zeile ändern, wenn Sie stattdessen TreeSet
verwenden möchten.
Außerdem sollten Methoden, die mit Mengen arbeiten, Parameter des Typs Set angeben:
public static void print(Set<String> s)
Dann kann die Methode für alle gesetzten Implementierungen verwendet werden .
Theoretisch sollten wir für verknüpfte Listen die gleiche Empfehlung aussprechen, nämlich LinkedList-Referenzen in Variablen vom Typ List zu speichern. In der Bibliothek Java ist die List-Schnittstelle jedoch sowohl der Klasse ArrayList
als auch der Klasse LinkedList
gemeinsam. Insbesondere werden Methoden für den wahlfreien Zugriff abgerufen und festgelegt, obwohl diese Methoden für verknüpfte Listen sehr ineffizient sind.
Sie können keinen effizienten Code schreiben , wenn Sie nicht wissen, ob der Direktzugriff effizient ist oder nicht.
Dies ist eindeutig ein schwerwiegender Entwurfsfehler in der Standardbibliothek, und ich kann aus diesem Grund die Verwendung der Listenschnittstelle nicht empfehlen.
Um zu sehen, wie peinlich dieser Fehler ist, werfen Sie einen Blick auf den Quellcode der binarySearch
-Methode der -Sammlungen Klasse. Diese Methode verwendet einen List-Parameter, aber die binäre Suche macht für eine verknüpfte Liste keinen Sinn. Der Code versucht dann ungeschickt herauszufinden, ob es sich bei der Liste um eine verknüpfte Liste handelt, und wechselt dann zu einer linearen Suche!
Die Benutzeroberfläche Set
und die Benutzeroberfläche Map
sind gut gestaltet, und Sie sollten sie verwenden.
Ich benutze (2), wenn der Code der "Besitzer" der Liste ist. Dies gilt zum Beispiel nur für lokale Variablen. Es gibt keinen Grund, den abstrakten Typ List
anstelle von ArrayList
zu verwenden. Ein weiteres Beispiel, um den Besitz zu demonstrieren:
public class Test {
// This object is the owner of strings, so use the concrete type.
private final ArrayList<String> strings = new ArrayList<>();
// This object uses the argument but doesn't own it, so use abstract type.
public void addStrings(List<String> add) {
strings.addAll(add);
}
// Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
public List<String> getStrings() {
return Collections.unmodifiableList(strings);
}
// Here we create a new list and give ownership to the caller. Use concrete type.
public ArrayList<String> getStringsCopy() {
return new ArrayList<>(strings);
}
}
Wenn Sie List
schreiben, sagen Sie tatsächlich, dass Ihr Objekt nur die Schnittstelle List
implementiert, aber Sie geben nicht an, zu welcher Klasse Ihr Objekt gehört.
Wenn Sie ArrayList
schreiben, geben Sie an, dass Ihre Objektklasse ein Array mit veränderbarer Größe ist.
Die erste Version macht Ihren Code also in Zukunft flexibler.
Schauen Sie sich die Java Dokumente an:
Class ArrayList
- Implementierung des List
-Interfaces in einem Array mit veränderbarer Größe.
Interface List
- Eine geordnete Sammlung (auch als Sequenz bezeichnet). Der Benutzer dieser Schnittstelle hat die genaue Kontrolle darüber, wo in der Liste jedes Element eingefügt wird.
Array
- Containerobjekt, das eine feste Anzahl von Werten eines einzelnen Typs enthält.
(3) Sammlung myCollection = new ArrayList ();
Ich benutze dies in der Regel. Und nur Wenn ich List-Methoden benötige, verwende ich List. Dasselbe gilt für ArrayList. Sie können immer zu einer "engeren" Benutzeroberfläche wechseln, aber nicht zu einer "breiteren".
Ich denke, die Leute, die (2) verwenden, kennen das Liskov-Substitutionsprinzip oder das Abhängigkeitsinversionsprinzip nicht. Oder sie müssen wirklich ArrayList
verwenden.
Tatsächlich gibt es Fälle, in denen (2) nicht nur bevorzugt, sondern obligatorisch ist, und ich bin sehr überrascht, dass dies hier nicht erwähnt wird.
Serialisierung!
Wenn Sie eine serialisierbare Klasse haben und möchten, dass sie eine Liste enthält, müssen Sie das Feld als konkreten und serialisierbaren Typ wie ArrayList
deklarieren, da die Schnittstelle List
nicht erweitert wird. _Java.io.Serializable
_
Offensichtlich brauchen die meisten Leute keine Serialisierung und vergessen dies.
Ein Beispiel:
_public class ExampleData implements Java.io.Serializable {
// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
_
Von den folgenden zwei:
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Erstens ist im Allgemeinen bevorzugt. Da Sie nur Methoden von der Schnittstelle List
verwenden, haben Sie die Freiheit, eine andere Implementierung von List
zu verwenden, z. LinkedList
in Zukunft. Das entkoppelt Sie also von der konkreten Implementierung. Nun sind zwei Punkte zu erwähnen:
ArrayList
über LinkedList
verwenden. Mehr hier .Ich frage mich, ob jemand verwendet (2)
Ja manchmal (selten lesen). Wenn wir Methoden benötigen, die Teil der Implementierung von ArrayList
sind, aber nicht Teil der Schnittstelle List
. Zum Beispiel ensureCapacity
.
Außerdem, wie oft (und kann ich bitte ein Beispiel bekommen) erfordert die Situation tatsächlich die Verwendung von (1) über (2)
Fast immer bevorzugen Sie Option (1). Dies ist ein klassisches Entwurfsmuster in OOP, in dem Sie immer versuchen, Ihren Code von der spezifischen Implementierung und dem Programm zur Schnittstelle zu entkoppeln.
Jemand fragte dies noch einmal (Duplikat), was mich dazu brachte, dieses Thema etwas genauer zu behandeln.
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
ArrayList<String> aList = new ArrayList<String>();
aList.add("a");
aList.add("b");
}
Wenn wir einen Bytecode-Viewer verwenden (ich habe http://asm.ow2.org/Eclipse/index.html ), sehen wir für unseren liste schnipsel:
L0
LINENUMBER 9 L0
NEW ArrayList
DUP
INVOKESPECIAL ArrayList.<init> () : void
ASTORE 1
L1
LINENUMBER 10 L1
ALOAD 1: list
LDC "a"
INVOKEINTERFACE List.add (Object) : boolean
POP
L2
LINENUMBER 11 L2
ALOAD 1: list
LDC "b"
INVOKEINTERFACE List.add (Object) : boolean
POP
und für alist :
L3
LINENUMBER 13 L3
NEW Java/util/ArrayList
DUP
INVOKESPECIAL Java/util/ArrayList.<init> ()V
ASTORE 2
L4
LINENUMBER 14 L4
ALOAD 2
LDC "a"
INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
L5
LINENUMBER 15 L5
ALOAD 2
LDC "b"
INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
Der Unterschied ist, dass list am Ende INVOKEINTERFACE aufruft, während aList aufruft INVOKEVIRTUAL. Gemäß der Bycode Outline Plugin Referenz,
mit invokeinterface wird eine Methode aufgerufen, die in einer Java -Schnittstelle deklariert ist
während invokevirtual
ruft alle Methoden auf, mit Ausnahme von Schnittstellenmethoden (die invokeinterface verwenden), statischen Methoden (die invokestatic verwenden) und den wenigen Sonderfällen, die von invokespecial behandelt werden.
Zusammenfassend lässt sich sagen, dass invokevirtual objectref für invokeinterface vom Stapel springt
der Interpreter entfernt 'n' Elemente vom Operandenstapel, wobei 'n' ein 8-Bit-Integer-Parameter ohne Vorzeichen ist, der aus dem Bytecode stammt. Das erste dieser Elemente ist objectref, eine Referenz auf das Objekt, dessen Methode aufgerufen wird.
Wenn ich das richtig verstehe, ist der Unterschied im Grunde, wie jeder Weg objectref abruft.
List ist eine Schnittstelle. Es gibt keine Methoden. Wenn Sie method in einer List-Referenz aufrufen. In beiden Fällen wird die Methode von ArrayList aufgerufen.
Und für die Zukunft können Sie List obj = new ArrayList<>
in List obj = new LinkList<>
oder einen anderen Typ ändern, der List interface. implementiert
Der einzige Fall, von dem ich weiß, wo (2) kann sein besser ist, wenn ich GWT verwende, weil es den Platzbedarf für Anwendungen verringert (nicht meine Idee, aber das Google Web Toolkit-Team sagt es aus). Aber für reguläres Java Laufen in der JVM (1) ist wahrscheinlich immer besser.
Ich würde sagen, dass 1 bevorzugt wird, es sei denn
Ich vermute, dass Sie in 99% der Fälle mit List auskommen, was bevorzugt wird.
removeAll
oder add(null)
List
Die Benutzeroberfläche verfügt über verschiedene Klassen - ArrayList
und LinkedList
. LinkedList
wird verwendet, um eine indizierte Sammlung zu erstellen, und ArrayList
- um sortierte Listen zu erstellen. Sie können also eines davon in Ihren Argumenten verwenden, aber Sie können anderen Entwicklern, die Ihren Code, Ihre Bibliothek usw. verwenden, gestatten, verschiedene Arten von Listen zu verwenden, nicht nur die, die Sie bei dieser Methode verwenden
ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}
sie können es nur mit ArrayList
verwenden, nicht mit LinkedList
. Sie können jedoch zulassen, dass eine der Klassen List
an anderen Stellen verwendet wird, an denen die Methode verwendet wird Schnittstelle kann es ermöglichen:
List<Object> myMethod (List<Object> input) {
// body
}
In diesen Methodenargumenten können Sie jede der List
Klassen verwenden, die Sie verwenden möchten:
List<Object> list = new ArrayList<Object> ();
list.add ("string");
myMethod (list);
FAZIT:
Verwenden Sie die Schnittstellen überall, wenn es möglich ist. Beschränken Sie sich oder andere nicht, andere Methoden zu verwenden, die sie verwenden möchten.