Pourquoi Collection.remove (Object o) n'est-il pas générique?
On dirait que Collection<E>
j'aurais puboolean remove(E o);
Ensuite, lorsque vous essayez accidentellement de supprimer (par exemple) Set<String>
au lieu de chaque chaîne individuelle de a Collection<String>
, ce serait une erreur de compilation au lieu d'un problème de débogage plus tard.
java
api
generics
collections
Chris Mazzola
la source
la source
Réponses:
Josh Bloch et Bill Pugh font référence à ce problème dans Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone et Revenge of The Shift .
Josh Bloch dit (6:41) qu'ils ont tenté de générer la méthode get de Map, la méthode remove et une autre, mais "cela n'a tout simplement pas fonctionné".
Il y a trop de programmes raisonnables qui ne pourraient pas être générés si vous n'autorisez que le type générique de la collection comme type de paramètre. L'exemple qu'il donne est une intersection de a
List
deNumber
s et de aList
deLong
s.la source
remove()
(inMap
ainsi que inCollection
) n'est pas générique car vous devriez pouvoir passer n'importe quel type d'objet àremove()
. L'objet supprimé n'a pas besoin d'être du même type que l'objet auquel vous passezremove()
; il faut seulement qu'ils soient égaux. De la spécification deremove()
,remove(o)
supprime l'objete
tel qu'il(o==null ? e==null : o.equals(e))
esttrue
. Notez qu'il n'y a rien d'exigero
ete
d'être du même type. Cela découle du fait que laequals()
méthode prend unObject
paramètre as, pas seulement le même type que l'objet.Bien qu'il puisse être communément vrai que de nombreuses classes se soient
equals()
définies de sorte que ses objets ne puissent être égaux qu'aux objets de sa propre classe, ce n'est certainement pas toujours le cas. Par exemple, la spécification deList.equals()
indique que deux objets List sont égaux s'ils sont tous les deux des Lists et ont le même contenu, même s'il s'agit d'implémentations différentes deList
. Donc, pour revenir à l'exemple de cette question, il est possible d'avoir unMap<ArrayList, Something>
et pour moi d'appelerremove()
avec unLinkedList
comme argument, et cela devrait supprimer la clé qui est une liste avec le même contenu. Cela ne serait pas possible s'ilremove()
était générique et restreint son type d'argument.la source
equals()
méthode également? Je pourrais voir plus d'avantages à taper la sécurité au lieu de cette approche «libertaire». Je pense que la plupart des cas d'implémentation actuelle concernent des bogues qui entrent dans notre code plutôt que la joie de cette flexibilitéremove()
qu'apporte la méthode.equals()
méthode"?T
doit être déclaré comme paramètre de type sur la classe etObject
n'a aucun paramètre de type. Il n'y a aucun moyen d'avoir un type qui fait référence à "la classe déclarante".Equality<T>
avecequals(T other)
. Alors vous pourriez avoirremove(Equality<T> o)
eto
n'est qu'un objet qui peut être comparé à un autreT
.Parce que si votre paramètre de type est un caractère générique, vous ne pouvez pas utiliser une méthode de suppression générique.
Il me semble me souvenir d'avoir rencontré cette question avec la méthode get (Object) de Map. La méthode get dans ce cas n'est pas générique, bien qu'elle devrait raisonnablement s'attendre à recevoir un objet du même type que le premier paramètre de type. J'ai réalisé que si vous transmettez Maps avec un caractère générique comme premier paramètre de type, il n'y a aucun moyen d'extraire un élément de la carte avec cette méthode, si cet argument était générique. Les arguments génériques ne peuvent pas vraiment être satisfaits, car le compilateur ne peut pas garantir que le type est correct. Je suppose que la raison pour laquelle ajouter est générique est que vous devez garantir que le type est correct avant de l'ajouter à la collection. Cependant, lors de la suppression d'un objet, si le type est incorrect, il ne correspondra à rien de toute façon.
Je ne l'ai probablement pas très bien expliqué, mais cela me semble assez logique.
la source
En plus des autres réponses, il y a une autre raison pour laquelle la méthode devrait accepter un
Object
, qui est des prédicats. Considérez l'exemple suivant:Le fait est que l'objet passé à la
remove
méthode est responsable de la définition de laequals
méthode. Construire des prédicats devient très simple de cette façon.la source
yourObject.equals(developer)
, comme documenté dans l'API Collections: java.sun.com/javase/6/docs/api/java/util/…equals
méthode, à savoir la symétrie. La méthode remove n'est liée à sa spécification que tant que vos objets remplissent la spécification equals / hashCode, donc toute implémentation serait libre de faire la comparaison dans l'autre sens. De plus, votre objet prédicat n'implémente pas la.hashCode()
méthode (ne peut pas implémenter de manière cohérente à égal à égal), donc l'appel de suppression ne fonctionnera jamais sur une collection basée sur Hash (comme HashSet ou HashMap.keys ()). Que cela fonctionne avec ArrayList est un pur hasard.Collection.remove
, sans rompre son contrat (si la commande est cohérente à égale). Et une ArrayList variée (ou AbstractCollection, je pense) avec l'appel égal retourné implémenterait toujours correctement le contrat - c'est votre faute si cela ne fonctionne pas comme prévu, puisque vous rompez leequals
contrat.Supposons que l' on a une collection de
Cat
, et quelques références d'objets de typesAnimal
,Cat
,SiameseCat
etDog
. Demander à la collection si elle contient l'objet auquel se réfère laCat
ou laSiameseCat
référence semble raisonnable. Demander s'il contient l'objet auquel laAnimal
référence fait référence peut sembler douteux, mais cela reste parfaitement raisonnable. L'objet en question pourrait, après tout, être unCat
, et pourrait apparaître dans la collection.De plus, même si l'objet se trouve être autre chose qu'un
Cat
, il n'y a aucun problème à dire s'il apparaît dans la collection - répondez simplement "non, ce n'est pas le cas". Une collection "lookup-style" d'un certain type devrait être capable d'accepter de manière significative la référence de n'importe quel supertype et de déterminer si l'objet existe dans la collection. Si la référence d'objet transmise est d'un type non lié, il n'y a aucun moyen que la collection puisse la contenir, donc la requête n'est pas significative dans un certain sens (elle répondra toujours "non"). Néanmoins, comme il n'y a aucun moyen de restreindre les paramètres à des sous-types ou des supertypes, il est plus pratique d'accepter simplement n'importe quel type et de répondre "non" pour tous les objets dont le type n'est pas lié à celui de la collection.la source
Comparable
est paramétré pour les types avec lesquels vous pouvez comparer). Il ne serait alors pas raisonnable de permettre aux gens de passer quelque chose d'un type sans rapport.A
etB
d'un type, etX
etY
d'un autre, tels queA
>B
, etX
>Y
. SoitA
>Y
etY
<A
, soitX
>B
etB
<X
. Ces relations ne peuvent exister que si les comparaisons de magnitude connaissent les deux types. En revanche, la méthode de comparaison d'égalité d'un objet peut simplement se déclarer inégal à tout autre type, sans avoir à savoir quoi que ce soit sur l'autre type en question. Un objet de typeCat
peut ne pas savoir si c'est ...FordMustang
, mais il ne devrait avoir aucune difficulté à dire s'il est égal à un tel objet (la réponse, évidemment, étant "non").J'ai toujours pensé que c'était parce que remove () n'avait aucune raison de se soucier du type d'objet que vous lui donniez. Il est assez facile, peu importe, de vérifier si cet objet est l'un de ceux que contient la collection, car il peut appeler equals () sur n'importe quoi. Il est nécessaire de vérifier le type sur add () pour s'assurer qu'il ne contient que des objets de ce type.
la source
C'était un compromis. Les deux approches ont leur avantage:
remove(Object o)
remove(E e)
apporte plus de sécurité de type à ce que la plupart des programmes veulent faire en détectant des bogues subtils au moment de la compilation, comme essayer par erreur de supprimer un entier d'une liste de courts-circuits.La rétrocompatibilité a toujours été un objectif majeur lors de l'évolution de l'API Java, c'est pourquoi remove (Object o) a été choisi car il facilitait la génération du code existant. Si la rétrocompatibilité n'avait PAS été un problème, je suppose que les concepteurs auraient choisi remove (E e).
la source
Remove n'est pas une méthode générique de sorte que le code existant utilisant une collection non générique sera toujours compilé et aura toujours le même comportement.
Voir http://www.ibm.com/developerworks/java/library/j-jtp01255.html pour plus de détails.
Edit: un commentateur demande pourquoi la méthode add est générique. [... supprimé mon explication ...] Le deuxième commentateur a répondu à la question de firebird84 bien mieux que moi.
la source
Une autre raison est à cause des interfaces. Voici un exemple pour le montrer:
la source
remove()
n'est pas covariant. La question, cependant, est de savoir si cela devrait être autorisé.ArrayList#remove()
fonctionne par le biais de l'égalité des valeurs et non de l'égalité de référence. Pourquoi vous attendez-vous à ce que aB
soit égal à unA
? Dans votre exemple, cela peut l' être, mais c'est une attente étrange. Je préférerais que vous fournissiez unMyClass
argument ici.Parce que cela briserait le code existant (pré-Java5). par exemple,
Maintenant, vous pourriez dire que le code ci-dessus est faux, mais supposons que o provienne d'un ensemble hétérogène d'objets (c'est-à-dire qu'il contenait des chaînes, des nombres, des objets, etc.). Vous voulez supprimer toutes les correspondances, ce qui était légal car remove ignorait simplement les non-chaînes car elles n'étaient pas égales. Mais si vous le supprimez (String o), cela ne fonctionne plus.
la source