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

Lesezeit: 11 Minuten

Was sind die Grunde warum Mapget Objektschlussel nicht vollstandig generisch
WMR

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

Um die Frage zu klären, ist die Signatur der Methode

V get(Object key)

anstatt

V get(K key)

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

  • Ähnliche Frage zur Sammlung: stackoverflow.com/questions/104799/…

    – AlikElzin-kilaka

    4. September 2012 um 12:37 Uhr

  • Mögliches Duplikat von Warum sind Methoden zum Entfernen von Java-Sammlungen nicht generisch?

    – Ole VV

    26. Oktober 2016 um 4:45 Uhr

  • Tolle. Ich verwende Java seit mehr als 20 Jahren und erkenne dieses Problem heute.

    – Geisterkatze

    9. August 2018 um 8:52 Uhr

1646639235 130 Was sind die Grunde warum Mapget Objektschlussel nicht vollstandig generisch
neues Konto

Wie von anderen erwähnt, der Grund warum get()usw. ist nicht generisch, da der Schlüssel des Eintrags, den Sie abrufen, nicht vom gleichen Typ sein muss wie das Objekt, an das Sie übergeben get(); die Spezifikation der Methode verlangt lediglich, dass sie gleich sind. Dies folgt daraus, wie die equals() -Methode nimmt ein Objekt als Parameter auf, nicht nur den gleichen Typ wie das Objekt.

Obwohl es im Allgemeinen zutreffen mag, dass viele Klassen dies haben equals() so definiert ist, dass seine Objekte nur Objekten seiner eigenen Klasse entsprechen können, gibt es viele Stellen in Java, an denen dies nicht der Fall ist. Zum Beispiel die Spezifikation für List.equals() sagt, dass zwei List-Objekte gleich sind, wenn sie beide Listen sind und denselben Inhalt haben, auch wenn sie unterschiedliche Implementierungen von sind List. Um auf das Beispiel in dieser Frage zurückzukommen, ist es gemäß der Spezifikation der Methode möglich, a zu haben Map<ArrayList, Something> und für mich anzurufen get() mit einer LinkedList als Argument, und es sollte den Schlüssel abrufen, der eine Liste mit demselben Inhalt ist. Dies wäre nicht möglich, wenn get() waren generisch und schränkten ihren Argumenttyp ein.

  • Warum ist dann V Get(K k) in C#?

    Benutzer166390

    14. September 2011 um 15:21 Uhr

  • Die Frage ist, ob Sie anrufen möchten m.get(linkedList)warum hast du nicht definiert m‘s Typ als Map<List,Something>? Ich kann mir keinen Anwendungsfall vorstellen, in dem angerufen wird m.get(HappensToBeEqual) ohne die zu ändern Map Typ, um eine Schnittstelle zu erhalten, ist sinnvoll.

    – Elazar Leibowitsch

    14. Februar 2012 um 12:07 Uhr

  • Wow, schwerer Konstruktionsfehler. Sie erhalten auch keine Compiler-Warnung, vermasselt. Ich stimme Elazar zu. Wenn dies wirklich nützlich ist, was meiner Meinung nach häufig vorkommt, klingt ein getByEquals (Object key) vernünftiger …

    – mjs

    26. September 2012 um 12:25 Uhr


  • Diese Entscheidung scheint eher auf der Grundlage der theoretischen Reinheit als der Praktikabilität getroffen worden zu sein. Für die meisten Verwendungen würden Entwickler das Argument viel lieber durch den Vorlagentyp begrenzt sehen, als es unbegrenzt zu haben, um Grenzfälle wie den von newacct in seiner Antwort erwähnten zu unterstützen. Das Hinterlassen von Signaturen ohne Vorlagen schafft mehr Probleme als es löst.

    – Sam Goldberg

    17. Dezember 2013 um 18:03 Uhr


  • @newacct: „perfekt typsicher“ ist eine starke Behauptung für ein Konstrukt, das zur Laufzeit unvorhersehbar fehlschlagen kann. Beschränken Sie Ihre Sicht nicht auf Hash-Maps, die zufällig damit funktionieren. TreeMap kann fehlschlagen, wenn Sie Objekte des falschen Typs an übergeben get -Methode, kann aber gelegentlich passieren, z. B. wenn die Karte zufällig leer ist. Und noch schlimmer, im Falle einer Lieferung Comparator der compare -Methode (die eine generische Signatur hat!) mit Argumenten des falschen Typs ohne ungeprüfte Warnung aufgerufen werden. Dies ist kaputtes Verhalten.

    – Holger

    20. Oktober 2014 um 18:43 Uhr

Ein großartiger Java-Programmierer bei Google, Kevin Bourrillion, schrieb über genau dieses Problem in a Blogeintrag vor einiger Zeit (allerdings im Kontext von Set anstatt Map). Der relevanteste Satz:

Methoden des Java Collections Framework (und auch der Google Collections Library) schränken einheitlich die Typen ihrer Parameter niemals ein, es sei denn, es ist notwendig, um zu verhindern, dass die Sammlung beschädigt wird.

Ich bin mir nicht ganz sicher, ob ich grundsätzlich damit einverstanden bin – .NET scheint zum Beispiel in Ordnung zu sein, wenn der richtige Schlüsseltyp erforderlich ist – aber es lohnt sich, der Argumentation im Blogbeitrag zu folgen. (Nachdem .NET erwähnt wurde, lohnt es sich zu erklären, dass ein Teil des Grundes, warum es in .NET kein Problem darstellt, darin besteht, dass es die größer Problem in .NET mit begrenzter Varianz …)

  • Ich bin sicher, dass Josh Bloch irgendwo darüber geschrieben hat. Ein früherer Versuch hat den generischen Parameter für den Parameter verwendet, wurde aber als zu umständlich empfunden.

    – Tom Hawtin – Angelleine

    13. Mai 2009 um 12:19 Uhr

  • Apocalisp: Das stimmt nicht, die Situation ist immer noch dieselbe.

    – Kevin Bourrillion

    4. November 2009 um 16:24 Uhr

  • @ user102008 Nein, der Beitrag ist nicht falsch. Obwohl ein Integer und ein Double niemals gleich sein können, ist es dennoch eine faire Frage zu fragen, ob a Set<? extends Number> enthält den Wert new Integer(5).

    – Kevin Bourrillion

    19. Juni 2012 um 15:10 Uhr

  • Ich wollte noch nie die Mitgliedschaft in einem überprüfen Set<? extends Foo>. Ich habe sehr oft den Schlüsseltyp einer Karte geändert und war dann frustriert, dass der Compiler nicht alle Stellen finden konnte, an denen der Code aktualisiert werden musste. Ich bin wirklich nicht davon überzeugt, dass dies der richtige Kompromiss ist.

    – Porculus

    21. September 2012 um 20:52 Uhr

  • @EarthEngine: Es war schon immer kaputt. Das ist der springende Punkt – der Code ist kaputt, aber der Compiler kann ihn nicht abfangen.

    – Jon Skeet

    20. März 2013 um 6:46 Uhr

Der Vertrag wird so ausgedrückt:

Wenn diese Abbildung formaler eine Abbildung von einem Schlüssel k auf einen Wert v enthält, so dass (key==null ? k==null :
key.equals(k)), dann 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 Notwendig abhängig von der Klasse von k.

  • Es ist auch abhängig hashCode(). Ohne eine ordnungsgemäße Implementierung von hashCode() wäre eine gut implementierte equals() ist in diesem Fall eher nutzlos.

    – Rudolfson

    13. Mai 2009 um 11:51 Uhr

  • Ich denke, im Prinzip könnten Sie damit einen einfachen Proxy für einen Schlüssel verwenden, wenn die Neuerstellung des gesamten Schlüssels unpraktisch wäre – solange equals () und hashCode () korrekt implementiert sind.

    – Bill Michel

    13. Mai 2009 um 12:32 Uhr

  • @rudolfson: Soweit mir bekannt ist, ist nur eine HashMap auf den Hash-Code angewiesen, um den richtigen Bucket zu finden. Eine TreeMap verwendet zum Beispiel einen binären Suchbaum und kümmert sich nicht um hashCode().

    – Rauben

    13. Mai 2009 um 17:19 Uhr

  • Genau genommen, get() muss kein Argument vom Typ annehmen Object um den Kontakt zu befriedigen. Stellen Sie sich vor, die get-Methode wäre auf den Schlüsseltyp beschränkt K – Der Vertrag wäre weiterhin gültig. Natürlich werden Verwendungen verwendet, bei denen der Kompilierzeittyp keine Unterklasse von war K würde jetzt nicht kompilieren, aber das macht den Vertrag nicht ungültig, da Verträge implizit besprechen, was passiert, wenn der Code kompiliert wird.

    – BeeOnRope

    2. Juni 2016 um 23:09 Uhr

  • Wenn wir über den Vertrag sprechen, vor allem um key.equals(k)Ich denke, es ist wichtig, dass auch der Vertrag zu beachten Auch ermöglicht einfaches Werfen a ClassCastException ist key ist von einem inkompatiblen Typ. Dies verringert den Nutzen von remove(Object) als eine Art removeIf(Predicate), da dies von der Sammlungsimplementierung abhängt. Wenn wir vom Verhalten einer bestimmten Sammlungsimplementierung abhängig sind, sollte dies möglicherweise als Methode dieser bestimmten Implementierung verfügbar gemacht werden, und die Sammlungsschnittstelle sollte ein konsistentes Verhalten für alle Implementierungen aufweisen.

    – Jesse

    1. Februar um 14:07 Uhr

Was sind die Grunde warum Mapget Objektschlussel nicht vollstandig generisch
Erickson

Es ist eine Anwendung von Postelsches 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; der equals Methode ist auf der definiert Object Klasse und akzeptiert alle Object als Parameter. Daher ist es für Schlüsseläquivalenz und Operationen, die auf Schlüsseläquivalenz basieren, sinnvoll, alle zu akzeptieren Object Art.

Wenn eine Zuordnung Schlüsselwerte zurückgibt, behält sie so viele Typinformationen wie möglich bei, indem sie den Typparameter verwendet.

Ich denke, dieser Abschnitt des Generika-Tutorials erklärt die Situation (meine Betonung):

“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);
  ...
}

Obwohl dies sicherlich typsicher ist, entspricht es nicht dem ursprünglichen Vertrag der API.
Die Methode containsAll() funktioniert mit jeder Art von eingehender Sammlung. Es wird nur erfolgreich sein, wenn die eingehende Sammlung wirklich nur Instanzen von E enthält, aber:

  • Der statische Typ der eingehenden Auflistung kann sich unterscheiden, möglicherweise weil der Aufrufer den genauen Typ der übergebenen Auflistung nicht kennt oder weil es sich um eine Collection handelt, wobei S ein Untertyp von E ist.
  • Es ist völlig legitim, containsAll() mit einer Sammlung eines anderen Typs aufzurufen. Die Routine sollte funktionieren und false zurückgeben.”

  • warum nicht containsAll( Collection< ? extends E > c )dann?

    – Richter Mental

    29. April 2013 um 23:20 Uhr

  • @JudgeMental, obwohl oben nicht als Beispiel angegeben, muss es auch zugelassen werden containsAll mit einer Collection<S> wo S ist ein Supertyp von E. Dies wäre nicht zulässig, wenn dies der Fall wäre containsAll( Collection< ? extends E > c ). Außerdem als ist ausdrücklich im Beispiel angegeben, ist es legitim, eine Sammlung eines anderen Typs zu übergeben (wobei der Rückgabewert dann false).

    – davmac

    10. Juni 2014 um 12:13 Uhr


  • Es sollte nicht notwendig sein, containsAll mit einer Sammlung eines Supertyps von E zuzulassen. Ich behaupte, dass es notwendig ist, diesen Aufruf mit einer statischen Typprüfung zu verbieten, um einen Fehler zu vermeiden. Es ist ein dummer Vertrag, was meiner Meinung nach der Punkt der ursprünglichen Frage ist.

    – Richter Mental

    10. Juni 2014 um 15:51 Uhr


Der Grund dafür ist, dass die Eindämmung durch bestimmt wird equals und hashCode welche Methoden sind auf Object und beide nehmen ein Object Parameter. Dies war ein früher Designfehler in den Standardbibliotheken von Java. In Verbindung mit Einschränkungen im Typsystem von Java zwingt es alles, was auf equals und hashCode angewiesen ist, zu nehmen Object.

Die einzige Möglichkeit, typsichere Hash-Tabellen und Gleichheit in Java zu haben, besteht darin, sie zu vermeiden Object.equals und Object.hashCode und verwenden Sie einen generischen Ersatz. Funktionales Java enthält Typklassen für genau diesen Zweck: Hash<A> und Equal<A>. Eine Hülle für HashMap<K, V> vorgesehen ist, dauert Hash<K> und Equal<K> in seinem Konstruktor. Die dieser Klasse get und contains Methoden nehmen daher ein generisches Typ-Argument entgegen 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

  • warum nicht containsAll( Collection< ? extends E > c )dann?

    – Richter Mental

    29. April 2013 um 23:20 Uhr

  • @JudgeMental, obwohl oben nicht als Beispiel angegeben, muss es auch zugelassen werden containsAll mit einer Collection<S> wo S ist ein Supertyp von E. Dies wäre nicht zulässig, wenn dies der Fall wäre containsAll( Collection< ? extends E > c ). Außerdem als ist ausdrücklich im Beispiel angegeben, ist es legitim, eine Sammlung eines anderen Typs zu übergeben (wobei der Rückgabewert dann false).

    – davmac

    10. Juni 2014 um 12:13 Uhr


  • Es sollte nicht notwendig sein, containsAll mit einer Sammlung eines Supertyps von E zuzulassen. Ich behaupte, dass es notwendig ist, diesen Aufruf mit einer statischen Typprüfung zu verbieten, um einen Fehler zu vermeiden. Es ist ein dummer Vertrag, was meiner Meinung nach der Punkt der ursprünglichen Frage ist.

    – Richter Mental

    10. Juni 2014 um 15:51 Uhr


1646639236 675 Was sind die Grunde warum Mapget Objektschlussel nicht vollstandig generisch
Herr Lister

Kompatibilität.

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

Hätten sie diese Methode in get( o) geändert, hätte dies den Java-Benutzern möglicherweise eine massive Codewartung aufgezwungen, nur um den funktionierenden Code erneut zu kompilieren.

Sie könnten haben eine eingeführt zusätzlich sagen Sie get_checked( o) und verwerfen Sie die alte Methode get(), damit es einen sanfteren Übergangspfad gibt. Aber aus irgendeinem Grund wurde dies nicht getan. (Die Situation, in der wir uns jetzt befinden, ist, dass Sie Tools wie findBugs installieren müssen, um die Typkompatibilität zwischen dem Argument get() und dem deklarierten Schlüsseltyp der Karte zu überprüfen.)

Die Argumente bezüglich der Semantik von .equals() sind meines Erachtens falsch. (Technisch gesehen sind sie korrekt, aber ich denke immer noch, dass sie falsch sind. Kein Designer, der bei klarem Verstand ist, wird jemals o1.equals(o2) wahr machen, wenn o1 und o2 keine gemeinsame Oberklasse haben.)

  • Aber auch dort wurde die Methode put(Object key, Object value) auf put(K key, V value) geändert, und das ist kein Problem!

    – Manuel Romeiro

    14. Juli 2020 um 16:50 Uhr

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

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy