Gibt es einen guten Grund, die Java Collection-Oberfläche zu verwenden?

11

Ich habe das Argument gehört, dass Sie die allgemeinste verfügbare Schnittstelle verwenden sollten, damit Sie nicht an eine bestimmte Implementierung dieser Schnittstelle gebunden sind. Gilt diese Logik für Schnittstellen wie java.util.Collection ?

Ich würde viel lieber Folgendes sehen:

List<Foo> getFoos()

oder

Set<Foo> getFoos()

Anstatt von

Collection<Foo> getFoos()

Im letzten Fall weiß ich nicht, um welche Art von Datensatz es sich handelt, während ich in den ersten beiden Fällen einige Annahmen über die Reihenfolge und Eindeutigkeit treffen kann. Hat java.util.Collection eine Nützlichkeit, die nicht darin besteht, ein logisches übergeordnetes Element für Mengen und Listen zu sein?

Wenn Sie bei einer Codeüberprüfung auf Code stoßen würden , der Collection verwendet, wie würden Sie feststellen, ob seine Verwendung gerechtfertigt ist, und welche Vorschläge würden Sie für den Ersatz durch eine spezifischere Schnittstelle machen?

Fil
quelle
3
Haben Sie in java.security.cert zufällig bemerkt, dass es sich um einen Rückgabetyp handelt Collection<List<?>>? Sprechen Sie über Coding Horror!
Macneil
1
@ Macneil Ich weiß nicht, auf welche Klasse Sie sich beziehen, aber ein solcher Rückgabetyp könnte in der Tat durchaus sinnvoll sein. Es sagt Ihnen im Wesentlichen, dass Sie eine Sammlung (dh eine Reihe von Dingen, die keine vernünftige Reihenfolge haben) von Listen (dh mit Dingen, die eine vernünftige Reihenfolge haben) von Objekten (dh Gegenständen, deren Typen wir statisch nicht kennen) haben welcher Grund auch immer). Kommt mir nicht unvernünftig vor.
Zero3

Antworten:

13

Abstraktionen leben länger als Implementierungen

Je abstrakter Ihr Design ist, desto länger bleibt es wahrscheinlich nützlich. Da Collection abstrakter ist als seine Unterschnittstellen, bleibt ein auf Collection basierendes API-Design mit größerer Wahrscheinlichkeit nützlich als ein auf List basierendes.

Das übergeordnete Prinzip besteht jedoch darin, die am besten geeignete Abstraktion zu verwenden . Wenn Ihre Sammlung geordnete Elemente unterstützen muss, müssen Sie eine Liste erstellen. Wenn keine Duplikate vorhanden sein sollen, müssen Sie einen Satz festlegen und so weiter.

Ein Hinweis zum generischen Interface-Design

Da Sie an der Verwendung der Collection-Oberfläche mit Generika interessiert sind, kann Folgendes hilfreich sein. Effektives Java von Joshua Bloch empfiehlt den folgenden Ansatz, wenn Sie eine Schnittstelle entwerfen, die auf Generika basiert: Producers Extend, Consumers Super

Dies wird auch als PECS-Regel bezeichnet . Wenn generische Sammlungen, die Daten erzeugen, an Ihre Klasse übergeben werden, sollte die Signatur im Wesentlichen folgendermaßen aussehen:

public void pushAll(Collection<? extends E> producerCollection) {}

Somit kann der Eingabetyp E oder eine beliebige Unterklasse von E sein (E ist in der Java-Sprache sowohl als Super- als auch als Unterklasse von sich selbst definiert).

Umgekehrt sollte eine generische Sammlung, die zum Konsumieren von Daten übergeben wird, eine folgende Signatur haben:

public void popAll(Collection<? super E> consumerCollection) {}

Die Methode wird mit jeder Superklasse von E korrekt umgehen. Insgesamt wird Ihre Benutzeroberfläche durch die Verwendung dieses Ansatzes für Ihre Benutzer weniger überraschend, da Sie in der Lage sind, sie korrekt zu übergeben Collection<Number>und Collection<Integer>behandeln zu lassen.

Gary Rowe
quelle
6

Die CollectionBenutzeroberfläche und die freizügigste Form Collection<?>eignen sich hervorragend für Parameter , die Sie akzeptieren. Basierend auf der Verwendung in der Java-Bibliothek selbst ist dies als Parametertyp häufiger als als Rückgabetyp.

Für Rückgabetypen denke ich, dass Ihr Punkt gültig ist: Wenn von Personen erwartet wird, dass sie darauf zugreifen, sollten sie die Reihenfolge (im Big-O-Sinne) der ausgeführten Operation kennen. Ich würde eine Collectionzurückgegebene Datei durchlaufen und sie einer anderen Sammlung hinzufügen, aber es wäre ein bisschen verrückt, sie aufzurufen contains, ohne zu wissen, ob es sich um eine O (1) -, O (log n) - oder O (n) -Operation handelt. Nur weil Sie ein haben Set, heißt das natürlich nicht, dass es sich um ein Hashset oder ein sortiertes Set handelt, aber irgendwann werden Sie davon ausgehen, dass die Schnittstelle angemessen implementiert wurde (und dann müssen Sie zu Plan B gehen, wenn Sie davon ausgehen wird als falsch angezeigt).

Wie Tom erwähnt, müssen Sie manchmal a zurückgeben Collection, um die Kapselung aufrechtzuerhalten: Sie möchten nicht, dass Implementierungsdetails herauskommen, selbst wenn Sie etwas Spezifischeres zurückgeben könnten. Oder, in dem von Tom erwähnten Fall, könnten Sie den spezifischeren Container zurückgeben, aber dann müssten Sie ihn konstruieren.

Macneil
quelle
2
Ich denke, dass der zweite Punkt etwas schwach ist. Sie wissen nicht, wie die Sammlung funktionieren wird, unabhängig davon, ob es sich um eine Sammlung oder eine Liste handelt - da es sich nur um Abstraktionen handelt. Wenn Sie keine konkrete Abschlussklasse haben, können Sie es nicht wirklich sagen.
Mark H
Wenn Sie nur wissen, dass es sich bei einer Sammlung um eine Sammlung handelt, wissen Sie nicht, ob sie Duplikate enthalten kann oder nicht. Wenn Sie eine Umkehrung vornehmen, die keine Duplikate enthält und keine signifikante Reihenfolge aufweist (was natürlich eine Menge wäre), aber aus irgendeinem guten Grund die Implementierung der Rückgabemethode verwendet eine Liste. Sie möchten keine Liste zurückgeben, da dies eine signifikante Reihenfolge impliziert, aber Sie können ein Set nicht zurückgeben, ohne das Rigmarol einer solchen zu durchlaufen. Sie geben also eine Sammlung zurück.
Tom Anderson
@ Tom: Guter Punkt!
Macneil
5

Ich würde es aus dem völlig entgegengesetzten Blickwinkel betrachten und fragen:

Wenn Sie bei einer Codeüberprüfung auf Code stoßen würden, der List <> verwendet, wie würden Sie feststellen, ob seine Verwendung gerechtfertigt ist?

Es ist ziemlich einfach, dies zu rechtfertigen. Sie verwenden die Liste, wenn Sie Funktionen benötigen, die von einer Sammlung nicht angeboten werden. Wenn Sie diese zusätzliche Funktionalität nicht benötigen - welche Rechtfertigung haben Sie? (Und ich werde nicht kaufen, "Ich würde es lieber sehen")

Es gibt viele Fälle, in denen Sie eine Sammlung für schreibgeschützte Zwecke verwenden, alles auf einmal füllen und vollständig durchlaufen - müssen Sie das Objekt jemals manuell indizieren?

Um ein echtes Beispiel zu geben. Angenommen, ich führe eine einfache Abfrage für eine Datenbank durch. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Ich nehme die relevanten Daten zurück, fülle Personenobjekte aus und füge sie in eine Personenliste ein.)

  • Muss ich per Index zugreifen? - Wäre bedeutungslos. Es gibt keine Zuordnung zwischen Index und ID.
  • Muss ich an einem Index einfügen? - Nein, das DBMS entscheidet, wo es abgelegt wird, wenn ich überhaupt etwas hinzufüge.

Welche Rechtfertigung hätte ich für diese zusätzlichen Methoden? (was in meiner PersonList sowieso nicht implementiert wäre)

Mark H.
quelle
Gutes Argument. Ich nehme an, meine Frage bezieht sich auf einen bestimmten Fall, in dem ich während einer Codeüberprüfung immer wieder DAOs sehe, die Sammlungen zurückgeben, aber ich weiß, dass diese DAO-Aufrufe immer Gruppen von Entitäten zurückgeben werden. Ich behaupte, dass in diesen Fällen der Rückgabetyp die Eindeutigkeit anzeigt und diese Informationen für jeden hilfreich sind, der diese Methode verwenden muss (z. B. muss ich nicht nach Duplikaten suchen).
Fil
Wenn Sie eine Datenbank abgefragt haben, sollte der Vergleich von 2 resultierenden Objekten mit equals () überhaupt nicht true ergeben. Sie benötigen also eine andere Möglichkeit, die Objekte für die Duplizierung zu vergleichen (z. B. haben sie denselben Namen, dieselbe ID, beide?). Sie müssen Ihrem DAO mitteilen, wie sie verglichen werden sollen, um Duplikate selbst zu entfernen. Da Sie jedoch der Benutzer sind, der entscheidet, ob Duplikate vorhanden sind, ist es einfacher, dies einfach mit der Sammlung aus dem aufrufenden Code zu tun. (Um mehr Abstraktionsebenen zu vermeiden und das DAO darüber zu informieren, wie jede mögliche Gleichstellungsprüfung auf dem Planeten durchgeführt werden soll.)
Mark H
Einverstanden, aber wir verwenden den Ruhezustand und stellen sicher, dass unsere Entitäten equals () implementieren. Wenn ein DAO Entitäten zurückgibt, können wir sehr schnell neues HashSet (). AddAll (Ergebnisse) ausführen und dieses an die aufgerufene Methode zurückgeben.
Fil