Warum sollte die Schnittstelle für eine Java-Klasse bevorzugt werden?

75

PMD würde einen Verstoß melden für:

ArrayList<Object> list = new ArrayList<Object>();

Die Verletzung lautete "Vermeiden Sie die Verwendung von Implementierungstypen wie 'ArrayList'; verwenden Sie stattdessen die Schnittstelle".

Die folgende Zeile würde den Verstoß korrigieren:

List<Object> list = new ArrayList<Object>();

Warum sollte letzteres mit Liststatt verwendet werden ArrayList?

jnancheta
quelle

Antworten:

80

Die Verwendung von Schnittstellen über konkreten Typen ist der Schlüssel für eine gute Kapselung und für die lose Kopplung Ihres Codes.

Es ist sogar eine gute Idee, diese Vorgehensweise beim Schreiben eigener APIs zu befolgen. Wenn Sie dies tun, werden Sie später feststellen, dass es einfacher ist, Ihrem Code Komponententests hinzuzufügen (mithilfe von Mocking-Techniken) und die zugrunde liegende Implementierung bei Bedarf in Zukunft zu ändern.

Hier ist ein guter Artikel zu diesem Thema.

Ich hoffe es hilft!

kolrie
quelle
1
Und was ist mit "Sammlung" anstelle von "Liste", also gehen wir in der Abstraktion noch einen Schritt weiter ...?
user1156544
31

Dies wird bevorzugt, weil Sie Ihren Code von der Implementierung der Liste entkoppeln. Mit der Schnittstelle können Sie die Implementierung, in diesem Fall ArrayList, problemlos in eine andere Listenimplementierung ändern, ohne den Rest des Codes zu ändern, solange nur die in List definierten Methoden verwendet werden.

AdamC
quelle
14

Im Allgemeinen stimme ich zu, dass die Entkopplung der Schnittstelle von der Implementierung eine gute Sache ist und die Wartung Ihres Codes erleichtert.

Es gibt jedoch Ausnahmen, die Sie berücksichtigen müssen. Durch den Zugriff auf Objekte über Schnittstellen wird eine zusätzliche Indirektionsebene hinzugefügt, die Ihren Code langsamer macht.

Aus Interesse habe ich ein Experiment durchgeführt, bei dem zehn Milliarden sequenzielle Zugriffe auf eine ArrayList mit einer Länge von 1 Million generiert wurden. Auf meinem 2,4-GHz-MacBook dauerte der Zugriff auf die ArrayList über eine List-Oberfläche durchschnittlich 2,10 Sekunden, bei der Deklaration vom Typ ArrayList durchschnittlich 1,67 Sekunden.

Wenn Sie mit großen Listen arbeiten, die sich tief in einer inneren Schleife befinden oder häufig als Funktion bezeichnet werden, sollten Sie dies berücksichtigen.

Owen
quelle
@Owen: +5 Insightful re: Leistungsunterschied ... Sehr unerwartet
Ande Turner
2
Diese Antwort zeigt jedoch, dass der Overhead sehr gering sein kann: stackoverflow.com/questions/890687/…
Raedwald
1
Beeindruckend! 0,5 Sekunden für zehn Milliarden Zugriffe, dh 1 Schnittstellenzugriff ist eine halbe Nanosekunde langsamer als ein Klassenzugriff! Dies ist natürlich ein Grund, niemals Schnittstellen zu verwenden.
Ingo
6

ArrayList und LinkedList sind zwei Implementierungen einer Liste, bei der es sich um eine geordnete Sammlung von Elementen handelt. In logischer Hinsicht spielt es keine Rolle, ob Sie eine ArrayList oder eine LinkedList verwenden, daher sollten Sie den Typ nicht darauf beschränken.

Dies steht im Gegensatz zu "Sammlung" und "Liste", die unterschiedliche Dinge sind (Liste impliziert Sortieren, Sammlung nicht).

SCdF
quelle
2

Warum sollte letzteres mit List anstelle von ArrayList verwendet werden?

Es ist eine gute Praxis: Programm zur Schnittstelle statt zur Implementierung

Durch Ersetzen ArrayListdurch Listkönnen Sie die ListImplementierung in Zukunft abhängig von Ihrem Geschäftsanwendungsfall wie folgt ändern .

List<Object> list = new  LinkedList<Object>(); 
/* Doubly-linked list implementation of the List and Deque interfaces. 
 Implements all optional list operations, and permits all elements (including null).*/

ODER

List<Object> list = new  CopyOnWriteArrayList<Object>(); 
/* A thread-safe variant of ArrayList in which all mutative operations
 (add, set, and so on) are implemented by making a fresh copy of the underlying array.*/

ODER

List<Object> list = new  Stack<Object>(); 

/* The Stack class represents a last-in-first-out (LIFO) stack of objects.*/

ODER

eine andere Listspezifische Implementierung.

ListSchnittstelle definiert Vertrag und spezifische Implementierung von Listkann geändert werden. Auf diese Weise sind Schnittstelle und Implementierung lose miteinander verbunden.

Verwandte SE-Frage:

Was bedeutet es, auf eine Schnittstelle zu programmieren?

Ravindra Babu
quelle
Und was ist mit "Sammlung" anstelle von "Liste", so dass man zum Beispiel Liste oder Set verwenden kann
user1156544
1

Selbst für lokale Variablen hilft die Verwendung der Schnittstelle über der konkreten Klasse. Möglicherweise rufen Sie eine Methode auf, die sich außerhalb der Schnittstelle befindet, und es ist dann schwierig, die Implementierung der Liste bei Bedarf zu ändern. Es ist auch am besten, die am wenigsten spezifische Klasse oder Schnittstelle in einer Deklaration zu verwenden. Wenn die Reihenfolge der Elemente keine Rolle spielt, verwenden Sie eine Sammlung anstelle einer Liste. Das gibt Ihrem Code maximale Flexibilität.

Diastrophismus
quelle
1

Die Eigenschaften Ihrer Klassen / Schnittstellen sollten über Schnittstellen verfügbar gemacht werden, da Ihre Klassen unabhängig von der Implementierung einen Verhaltensvertrag erhalten.

Jedoch...

In lokalen Variablendeklarationen ist dies wenig sinnvoll:

public void someMethod() {
List theList = new ArrayList();
//do stuff with the list
}

Wenn es sich um eine lokale Variable handelt, verwenden Sie einfach den Typ. Es ist immer noch implizit auf die entsprechende Schnittstelle hochsetzbar, und Ihre Methoden sollten hoffentlich die Schnittstellentypen für ihre Argumente akzeptieren. Für lokale Variablen ist es jedoch absolut sinnvoll, den Implementierungstyp als Container zu verwenden, nur für den Fall, dass Sie die Implementierung benötigen. spezifische Funktionalität.

MetroidFan2002
quelle
1

Im Allgemeinen ist es für Ihre Codezeile nicht sinnvoll, sich mit Schnittstellen zu beschäftigen. Aber wenn wir über APIs sprechen, gibt es einen wirklich guten Grund. Ich habe eine kleine Klasse

class Counter {
    static int sizeOf(List<?> items) {
        return items.size();
    }
}

In diesem Fall ist die Verwendung der Schnittstelle erforderlich. Weil ich die Größe jeder möglichen Implementierung einschließlich meiner eigenen Gewohnheit zählen möchte . class MyList extends AbstractList<String>....

Rastislav Komara
quelle
Wenn Sie für Implementierungen wie ArrayList codieren, besteht die Möglichkeit, dass Sie Implementierungsmethoden verwenden, auch wenn Sie mit Schnittstellenmethoden dasselbe tun könnten. Dies würde Ihren Code an die Implementierung binden und es schwieriger machen, später zwischen Implementierungen zu wechseln.
Champ
0

Die Schnittstelle ist dem Endbenutzer zugänglich. Eine Klasse kann mehrere Schnittstellen implementieren. Benutzer, die einer bestimmten Schnittstelle ausgesetzt sind, haben Zugriff auf ein bestimmtes Verhalten, das in dieser bestimmten Schnittstelle definiert ist.

Eine Schnittstelle hat auch mehrere Implementierungen. Basierend auf dem Szenario arbeitet das System mit verschiedenen Szenarien (Implementierung der Schnittstelle).

Lassen Sie mich wissen, wenn Sie weitere Erklärungen benötigen.

Sandeep Garg
quelle
0

Die Schnittstelle ist in der Debugger-Ansicht häufig besser dargestellt als die konkrete Klasse.

Kiruahxh
quelle