it-swarm.com.de

Typ Liste vs Typ ArrayList in Java

(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.)

522
kji

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.

414
kgiannakakis

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.)

108
Stephen C

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:

enter image description here

(hauptsächlich für das Sammlungsdiagramm)

39
Maxim Shoustin

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.

23
nazar_art

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);
    }
}
12
mazatwork

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.

11
wzbozon

(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".

9
Pavel Vyazankin

Ich denke, die Leute, die (2) verwenden, kennen das Liskov-Substitutionsprinzip oder das Abhängigkeitsinversionsprinzip nicht. Oder sie müssen wirklich ArrayList verwenden.

9
True Soft

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<>();
_
9
raudi

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:

  1. Wir sollten immer auf Schnittstelle programmieren. Mehr hier .
  2. Sie werden am Ende fast immer 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.

7
i_am_zero

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.

3
toxicafunk

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

3

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.

2
Hans Westerbeek

Ich würde sagen, dass 1 bevorzugt wird, es sei denn

  • sie sind abhängig von der Implementierung des optionalen Verhaltens * in ArrayList. In diesem Fall ist die explizite Verwendung von ArrayList klarer
  • Sie verwenden die ArrayList in einem Methodenaufruf, für den ArrayList erforderlich ist, möglicherweise für optionales Verhalten oder Leistungsmerkmale

Ich vermute, dass Sie in 99% der Fälle mit List auskommen, was bevorzugt wird.

  • zum Beispiel removeAll oder add(null)
1
extraneon

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.

1
Acuna