webentwicklung-frage-antwort-db.com.de

Was sind die Gründe, warum Map.get (Objektschlüssel) nicht (vollständig) generisch ist

Was sind die Gründe für die Entscheidung, keine vollständig generische get-Methode in der Schnittstelle von Java.util.Map<K, V> zu haben.

Zur Klärung der Frage lautet die Signatur der Methode

V get(Object key)

anstatt

V get(K key)

und ich frage mich warum (dasselbe für remove, containsKey, containsValue).

393
WMR

Wie von anderen erwähnt, ist der Grund, warum get() usw. nicht generisch ist, da der Schlüssel des abgerufenen Eintrags nicht mit dem Typ des Objekts übereinstimmen muss, das Sie an get(); Die Spezifikation der Methode erfordert nur, dass sie gleich sind. Dies ergibt sich daraus, wie die equals() -Methode ein Objekt als Parameter verwendet und nicht nur den gleichen Typ wie das Objekt.

Obwohl es allgemein wahr sein kann, dass für viele Klassen equals() definiert ist, so dass ihre Objekte nur Objekten ihrer eigenen Klasse entsprechen können, gibt es in Java) viele Stellen, an denen dies der Fall ist Dies ist jedoch nicht der Fall. Beispielsweise besagt die Spezifikation für List.equals(), dass zwei List-Objekte gleich sind, wenn sie beide Listen sind und denselben Inhalt haben, auch wenn es sich um unterschiedliche Implementierungen von List handelt. Um auf das Beispiel in dieser Frage zurückzukommen, ist es gemäß der Spezifikation der Methode möglich, ein Map<ArrayList, Something> Zu haben und get() mit einem LinkedList als Argument aufzurufen , und es sollte den Schlüssel abrufen, der eine Liste mit demselben Inhalt ist. Dies wäre nicht möglich, wenn get() generisch wäre und seinen Argumenttyp einschränken würde.

258
newacct

Ein großartiger Java Codierer bei Google, Kevin Bourrillion, schrieb vor einer Weile über genau dieses Problem in einem Blog-Beitrag (zugegebenermaßen im Zusammenhang mit Set anstelle von Map). Der relevanteste Satz:

Gleichermaßen beschränken Methoden des Java Collections Framework (und der Google Collections Library)) niemals die Typen ihrer Parameter, es sei denn, dies ist erforderlich, um zu verhindern, dass die Sammlung beschädigt wird.

Ich bin nicht ganz sicher, ob ich dem grundsätzlich zustimme - .NET scheint zum Beispiel in Ordnung zu sein, wenn man den richtigen Schlüsseltyp benötigt -, aber es lohnt sich, den Überlegungen im Blog-Beitrag zu folgen. (Nachdem wir .NET erwähnt haben, ist es wert zu erklären, dass ein Teil des Grundes, warum es in .NET kein Problem ist, darin besteht, dass es das größere Problem in .NET gibt mehr begrenzte Varianz ...)

105
Jon Skeet

Der Vertrag kommt folgendermaßen zum Ausdruck:

Genauer gesagt, wenn diese Zuordnung eine Zuordnung von einem Schlüssel k zu einem Wert v enthält, sodass (key == null? K == null: key.equals (k)), gibt diese Methode v zurück ; Andernfalls wird null zurückgegeben. (Es kann höchstens eine solche Zuordnung geben.)

(meine Betonung)

daher hängt eine erfolgreiche Schlüsselsuche von der Implementierung der Gleichheitsmethode durch den Eingabeschlüssel ab. Das ist nicht unbedingt abhängig von der Klasse von k.

29
Brian Agnew

Es ist eine Anwendung von Postels Gesetz "Sei konservativ in dem, was du tust, sei liberal in dem, was du von anderen akzeptierst."

Gleichheitsprüfungen können unabhängig vom Typ durchgeführt werden. Die Methode equals ist in der Klasse Object definiert und akzeptiert alle Object als Parameter. Daher ist es sinnvoll, für die Schlüsseläquivalenz und für Operationen, die auf der Schlüsseläquivalenz basieren, jeden Object -Typ zu akzeptieren.

Wenn eine Map Schlüsselwerte zurückgibt, werden mithilfe des type-Parameters so viele Typinformationen wie möglich gespeichert.

17
erickson

Ich denke, dieser Abschnitt des Generics Tutorials erklärt die Situation (mein Schwerpunkt):

"Sie müssen sicherstellen, dass die generische API nicht übermäßig restriktiv ist. Sie muss weiterhin den ursprünglichen Vertrag der API unterstützen. Betrachten Sie noch einmal einige Beispiele aus Java.util.Collection. Die vorgenerische API sieht folgendermaßen aus:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

Ein naiver Versuch, es zu generieren, ist:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

Dies ist zwar sicher typsicher, entspricht aber nicht dem ursprünglichen Vertrag der API. Die containAll () -Methode funktioniert mit jeder Art von eingehender Sammlung. Es wird nur erfolgreich sein, wenn die eingehende Auflistung wirklich nur Instanzen von E enthält, aber:

  • Der statische Typ der eingehenden Sammlung kann abweichen, weil der Anrufer den genauen Typ der übergebenen Sammlung nicht kennt oder weil es sich um eine Sammlung <S> handelt, wobei S ein Subtyp von E ist.
  • Es ist absolut legitim, containAll () mit einer Sammlung eines anderen Typs aufzurufen. Die Routine sollte funktionieren und false zurückgeben. "
12
Yardena

Der Grund dafür ist, dass die Eindämmung durch equals und hashCode bestimmt wird, die Methoden für Object sind und beide einen Object - Parameter verwenden. Dies war ein früher Konstruktionsfehler in Javas Standardbibliotheken. Verbunden mit Einschränkungen in Javas Typsystem erzwingt es, dass alles, was auf equals und hashCode beruht, Object annimmt.

Die einzige Möglichkeit, typsichere Hash-Tabellen und Gleichheit in Java) zu haben, besteht darin, Object.equals Und Object.hashCode Zu vermeiden und einen generischen Ersatz zu verwenden. Functional Java enthält zu diesem Zweck Typklassen: Hash<A> und Equal<A> . Ein Wrapper für HashMap<K, V> wird bereitgestellt, die im Konstruktor Hash<K> und Equal<K> verwendet. Die Methoden get und contains dieser Klasse verwenden daher ein generisches Argument vom Typ K.

Beispiel:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error
6
Apocalisp

Es gibt noch einen gewichtigen Grund, es kann technisch nicht getan werden, weil es die Map kaputt macht.

Java hat eine polymorphe generische Konstruktion wie <? extends SomeClass>. Ein solcher Verweis kann auf einen mit <AnySubclassOfSomeClass> Signierten Typ verweisen. Aber polymorphes Generikum macht diese Referenz readonly. Der Compiler erlaubt es Ihnen, generische Typen nur als Rückgabetyp von Methoden zu verwenden (wie einfache Getter), blockiert jedoch die Verwendung von Methoden, bei denen generischer Typ ein Argument ist (wie gewöhnliche Setter). Wenn Sie also Map<? extends KeyType, ValueType> Schreiben, können Sie vom Compiler nicht die Methode get(<? extends KeyType>) aufrufen, und die Map ist unbrauchbar. Die einzige Lösung besteht darin, diese Methode nicht generisch zu machen: get(Object).

4
Owheee

Kompatibilität.

Bevor Generika verfügbar waren, gab es nur get (Object o).

Wenn sie diese Methode geändert hätten, um (<K> o) zu erhalten, hätte dies möglicherweise eine massive Codewartung für Java Benutzer erzwungen, nur um den Arbeitscode erneut kompilieren zu lassen.

Sie könnten haben eine zusätzliche Methode eingeführt, sagen get_checked (<K> o) und verwerfen die alte get () Methode, so dass es einen sanfteren Übergangspfad gab. Aber aus irgendeinem Grund wurde dies nicht getan. (Wir befinden uns derzeit in der Situation, dass Sie Tools wie findBugs installieren müssen, um die Typkompatibilität zwischen dem Argument get () und dem deklarierten Schlüsseltyp <K> der Map zu überprüfen.)

Die Argumente, die sich auf die Semantik von .equals () beziehen, sind meiner Meinung nach falsch. (Technisch gesehen sind sie korrekt, aber ich denke immer noch, dass sie falsch sind. Kein Designer, der bei Verstand ist, wird jemals o1.equals (o2) true machen, wenn o1 und o2 keine gemeinsame Superklasse haben.)

2
Erwin Smout

Ich habe mir das angeschaut und überlegt, warum sie es so gemacht haben. Ich glaube nicht, dass eine der vorhandenen Antworten erklärt, warum sie die neue generische Schnittstelle nicht dazu bringen konnten, nur den richtigen Typ für den Schlüssel zu akzeptieren. Der eigentliche Grund ist, dass sie, obwohl sie Generika eingeführt haben, KEINE neue Schnittstelle erstellt haben. Die Map-Oberfläche ist dieselbe alte, nicht generische Map, die nur als generische und nicht generische Version dient. Auf diese Weise können Sie einer Methode, die eine nicht generische Map akzeptiert, einen Map<String, Customer> Übergeben, und es würde weiterhin funktionieren. Gleichzeitig akzeptiert der Vertrag für get Object, sodass die neue Schnittstelle auch diesen Vertrag unterstützen sollte.

Meiner Meinung nach hätten sie eine neue Schnittstelle hinzufügen und beide in der vorhandenen Sammlung implementieren sollen, aber sie haben sich für kompatible Schnittstellen entschieden, auch wenn dies ein schlechteres Design für die get-Methode bedeutet. Beachten Sie, dass die Auflistungen selbst mit vorhandenen Methoden kompatibel wären, die Schnittstellen jedoch nicht.

1
Stilgar

Abwärtskompatibilität, denke ich. Map (oder HashMap) muss noch get(Object) unterstützen.

1
Anton Gogolev

Wir führen gerade große Umgestaltungen durch und haben dieses stark typisierte get () verpasst, um zu überprüfen, ob wir ein get () mit altem Typ nicht verpasst haben.

Aber ich habe einen Workaround/hässlichen Trick für die Überprüfung der Kompilierungszeit gefunden: Erstelle ein Map-Interface mit stark typisiertem get, enthältKey, entferne ... und füge es in das Java.util-Paket deines Projekts ein.

Sie erhalten Kompilierungsfehler, wenn Sie nur get () aufrufen. Bei falschen Typen scheint alles andere für den Compiler in Ordnung zu sein (zumindest in Eclipse kepler).

Vergessen Sie nicht, diese Schnittstelle nach der Überprüfung Ihres Builds zu löschen, da dies zur Laufzeit nicht gewünscht ist.

0
henva