Warum ist Collection.remove (Object o) nicht generisch?
Scheint wie Collection<E>
könnte habenboolean remove(E o);
Wenn Sie dann versehentlich versuchen, (zum Beispiel) Set<String>
anstelle jeder einzelnen Zeichenfolge aus a zu entfernen Collection<String>
, handelt es sich um einen Fehler bei der Kompilierung anstelle eines späteren Debugging-Problems.
java
api
generics
collections
Chris Mazzola
quelle
quelle
Antworten:
Josh Bloch und Bill Pugh verweisen auf dieses Problem in Java Puzzlers IV: Die Phantomreferenzbedrohung, Angriff des Klons und Rache der Verschiebung .
Josh Bloch sagt (6:41), dass sie versucht haben, die get-Methode von Map zu generieren, die Methode zu entfernen und einige andere, aber "es hat einfach nicht funktioniert".
Es gibt zu viele sinnvolle Programme, die nicht generiert werden könnten, wenn Sie nur den generischen Typ der Sammlung als Parametertyp zulassen. Das von ihm gegebene Beispiel ist ein Schnittpunkt von a
List
vonNumber
s und aList
vonLong
s.quelle
remove()
(inMap
sowie inCollection
) ist nicht generisch, da Sie in der Lage sein sollten, jede Art von Objekt an zu übergebenremove()
. Das entfernte Objekt muss nicht vom selben Typ sein wie das Objekt, an das Sie übergebenremove()
. es erfordert nur, dass sie gleich sind. Aus der Beschreibung derremove()
,remove(o)
entfernt das Objekt ,e
so dass(o==null ? e==null : o.equals(e))
isttrue
. Beachten Sie, dass nichts erforderlich isto
unde
vom selben Typ sein muss. Dies folgt aus der Tatsache, dass dieequals()
Methode einenObject
as-Parameter akzeptiert, nicht nur denselben Typ wie das Objekt.Obwohl es allgemein zutreffend sein mag, dass viele Klassen so
equals()
definiert haben, dass ihre Objekte nur Objekten seiner eigenen Klasse entsprechen können, ist dies sicherlich nicht immer der Fall. In der Spezifikation fürList.equals()
heißt es beispielsweise, dass zwei Listenobjekte gleich sind, wenn sie beide Listen sind und denselben Inhalt haben, auch wenn es sich um unterschiedliche Implementierungen von handeltList
. Wenn wir also auf das Beispiel in dieser Frage zurückkommen, ist es möglich, einMap<ArrayList, Something>
und für michremove()
mit einemLinkedList
as-Argument aufzurufen , und es sollte den Schlüssel entfernen, der eine Liste mit demselben Inhalt ist. Dies wäre nicht möglich, wennremove()
der Argumenttyp generisch und eingeschränkt wäre.quelle
equals()
Methode nicht auch generisiert haben . Ich könnte mehr Vorteile für die Typensicherheit sehen als für diesen "libertären" Ansatz. Ich denke, die meisten Fälle mit der aktuellen Implementierung sind eher auf Fehler zurückzuführen, die in unseren Code gelangen, als auf die Freude an dieser Flexibilität, die dieremove()
Methode mit sich bringt.equals()
method" meinen ?T
muss als Typparameter in der Klasse deklariert werden undObject
hat keine Typparameter. Es gibt keine Möglichkeit, einen Typ zu haben, der sich auf "die deklarierende Klasse" bezieht.Equality<T>
mitequals(T other)
. Dann könntenremove(Equality<T> o)
und haben Sieo
nur ein Objekt, das mit einem anderen verglichen werden kannT
.Wenn Ihr Typparameter ein Platzhalter ist, können Sie keine generische Entfernungsmethode verwenden.
Ich erinnere mich an diese Frage mit der get (Object) -Methode von Map. Die get-Methode ist in diesem Fall nicht generisch, sollte jedoch vernünftigerweise erwarten, dass ein Objekt des gleichen Typs wie der erste Typparameter übergeben wird. Ich habe festgestellt, dass es mit dieser Methode keine Möglichkeit gibt, ein Element aus der Map zu entfernen, wenn Sie Maps mit einem Platzhalter als erstem Typparameter übergeben, wenn dieses Argument generisch ist. Platzhalterargumente können nicht wirklich erfüllt werden, da der Compiler nicht garantieren kann, dass der Typ korrekt ist. Ich spekuliere, dass der Grund, warum add generisch ist, darin besteht, dass von Ihnen erwartet wird, dass Sie sicherstellen, dass der Typ korrekt ist, bevor Sie ihn der Sammlung hinzufügen. Wenn beim Entfernen eines Objekts der Typ jedoch falsch ist, stimmt er ohnehin mit nichts überein.
Ich habe es wahrscheinlich nicht sehr gut erklärt, aber es scheint mir logisch genug.
quelle
Zusätzlich zu den anderen Antworten gibt es einen weiteren Grund, warum die Methode ein
Object
Prädikat akzeptieren sollte . Betrachten Sie das folgende Beispiel:Der Punkt ist, dass das Objekt, das an die
remove
Methode übergeben wird, für die Definition derequals
Methode verantwortlich ist. Das Erstellen von Prädikaten wird auf diese Weise sehr einfach.quelle
yourObject.equals(developer)
in der Sammlungs-API dokumentiert implementiert : java.sun.com/javase/6/docs/api/java/util/…equals
Methode, nämlich die Symmetrie, bricht . Die Methode remove ist nur an ihre Spezifikation gebunden, solange Ihre Objekte die Spezifikation equals / hashCode erfüllen. Daher kann jede Implementierung den Vergleich auch umgekehrt durchführen. Außerdem implementiert Ihr Prädikatobjekt die.hashCode()
Methode nicht (kann nicht konsistent auf gleich implementiert werden), sodass der Aufruf zum Entfernen niemals für eine Hash-basierte Auflistung (wie HashSet oder HashMap.keys ()) funktioniert. Dass es mit ArrayList funktioniert, ist reines Glück.Collection.remove
, ohne den Vertrag zu brechen (wenn die Bestellung gleich ist). Und eine abwechslungsreiche ArrayList (oder AbstractCollection, glaube ich) mit dem gleichgestellten Aufruf würde den Vertrag immer noch korrekt implementieren - es ist Ihre Schuld, wenn er nicht wie beabsichtigt funktioniert, da Sie denequals
Vertrag brechen .Angenommen , man eine Sammlung von hat
Cat
, und einige Objektreferenzen von TypenAnimal
,Cat
,SiameseCat
, undDog
. Es erscheint vernünftig, die Sammlung zu fragen, ob sie das Objekt enthält, auf das sich die ReferenzCat
oder dieSiameseCat
Referenz bezieht. Die Frage, ob es das Objekt enthält, auf das sich dieAnimal
Referenz bezieht, mag zweifelhaft erscheinen, ist aber dennoch durchaus vernünftig. Das fragliche Objekt könnte schließlich ein seinCat
und in der Sammlung erscheinen.Selbst wenn das Objekt etwas anderes als a ist
Cat
, ist es kein Problem zu sagen, ob es in der Sammlung erscheint - antworten Sie einfach mit "Nein, tut es nicht". Eine Sammlung im "Lookup-Stil" eines Typs sollte in der Lage sein, Verweise auf einen beliebigen Supertyp sinnvoll zu akzeptieren und festzustellen, ob das Objekt in der Sammlung vorhanden ist. Wenn die übergebene Objektreferenz von einem nicht verwandten Typ ist, kann die Sammlung sie möglicherweise nicht enthalten, sodass die Abfrage in gewissem Sinne nicht aussagekräftig ist (sie antwortet immer mit "Nein"). Da es jedoch keine Möglichkeit gibt, Parameter auf Subtypen oder Supertypen zu beschränken, ist es am praktischsten, einfach einen beliebigen Typ zu akzeptieren und für Objekte, deren Typ nicht mit dem der Sammlung zusammenhängt, mit "Nein" zu antworten.quelle
Comparable
bei den Typen, mit denen Sie vergleichen können). Dann wäre es nicht vernünftig, Menschen zu erlauben, etwas von einem nicht verwandten Typ zu übergeben.A
undB
von einem Typ undX
undY
von einem anderen, so dassA
>B
undX
>Y
. EntwederA
>Y
undY
<A
oderX
>B
undB
<X
. Diese Beziehungen können nur bestehen, wenn die Größenvergleiche beide Typen kennen. Im Gegensatz dazu kann sich die Gleichheitsvergleichsmethode eines Objekts einfach für ungleich erklären, ohne dass irgendetwas über den anderen fraglichen Typ bekannt sein muss. Ein Objekt vom Typ hatCat
möglicherweise keine Ahnung, ob es ...FordMustang
, aber es sollte keine Schwierigkeit geben zu sagen, ob es einem solchen Objekt entspricht (die Antwort lautet offensichtlich "nein").Ich habe immer gedacht, dass dies daran liegt, dass remove () keinen Grund hat, sich darum zu kümmern, welche Art von Objekt Sie ihm geben. Unabhängig davon ist es einfach genug zu überprüfen, ob dieses Objekt eines der Objekte ist, die in der Sammlung enthalten sind, da es für alles equals () aufrufen kann. Es ist erforderlich, den Typ bei add () zu überprüfen, um sicherzustellen, dass nur Objekte dieses Typs enthalten sind.
quelle
Es war ein Kompromiss. Beide Ansätze haben ihren Vorteil:
remove(Object o)
remove(E e)
bringt mehr Typensicherheit für das, was die meisten Programme tun möchten, indem sie beim Kompilieren subtile Fehler erkennen, z. B. den irrtümlichen Versuch, eine Ganzzahl aus einer Liste von Kurzschlüssen zu entfernen.Die Abwärtskompatibilität war bei der Entwicklung der Java-API immer ein wichtiges Ziel. Daher wurde remove (Object o) ausgewählt, da dies das Generieren von vorhandenem Code erleichtert. Wenn die Abwärtskompatibilität KEIN Problem gewesen wäre, hätten die Designer vermutlich remove (E e) gewählt.
quelle
Entfernen ist keine generische Methode, sodass vorhandener Code, der eine nicht generische Sammlung verwendet, weiterhin kompiliert wird und dasselbe Verhalten aufweist.
Weitere Informationen finden Sie unter http://www.ibm.com/developerworks/java/library/j-jtp01255.html .
Bearbeiten: Ein Kommentator fragt, warum die Add-Methode generisch ist. [... meine Erklärung entfernt ...] Der zweite Kommentator beantwortete die Frage von firebird84 viel besser als ich.
quelle
Ein weiterer Grund sind Schnittstellen. Hier ist ein Beispiel, um es zu zeigen:
quelle
remove()
es nicht kovariant ist. Die Frage ist jedoch, ob dies sollte erlaubt sein.ArrayList#remove()
arbeitet als Wertgleichheit, nicht als Referenzgleichheit. Warum würden Sie erwarten, dass aB
gleich a istA
? In Ihrem Beispiel es kann sein, aber es ist eine seltsame Erwartung. Ich würde lieber sehen, dass SieMyClass
hier ein Argument liefern .Weil es vorhandenen Code (vor Java5) beschädigen würde. z.B,
Nun könnten Sie sagen, dass der obige Code falsch ist, aber nehmen Sie an, dass o aus einer heterogenen Menge von Objekten stammt (dh Zeichenfolgen, Zahlen, Objekte usw. enthält). Sie möchten alle Übereinstimmungen entfernen, was legal war, da beim Entfernen die Nicht-Zeichenfolgen einfach ignoriert wurden, weil sie nicht gleich waren. Aber wenn Sie es entfernen lassen (String o), funktioniert das nicht mehr.
quelle