web-dev-qa-db-fra.com

Pourquoi les collections Java Supprimer les méthodes ne sont-elles pas génériques?

Pourquoi Collection.remove (Object o) n'est-il pas générique?

Il semble que Collection<E> Pourrait avoir boolean remove(E o);

Ensuite, lorsque vous essayez accidentellement de supprimer (par exemple) Set<String> Au lieu de chaque chaîne individuelle d'un Collection<String>, Ce serait une erreur de compilation au lieu d'un problème de débogage plus tard.

138
Chris Mazzola

Josh Bloch et Bill Pugh font référence à ce problème dans Java Puzzlers IV: La menace de référence fantôme, l'attaque du clone et la vengeance du changement .

Josh Bloch dit (6:41) qu'ils ont tenté de générer la méthode get de Map, de supprimer la méthode et d'autres, mais "cela n'a tout simplement pas fonctionné".

Il existe trop de programmes raisonnables qui ne pourraient pas être générés si vous autorisez uniquement le type générique de la collection comme type de paramètre. L'exemple qu'il donne est une intersection d'un List de Numbers et d'un List de Longs.

68
dmeister

remove() (dans Map ainsi que dans Collection) n'est pas générique car vous devriez pouvoir passer n'importe quel type d'objet à remove(). L'objet supprimé ne doit pas nécessairement être du même type que l'objet que vous transmettez à remove(); il suffit qu'ils soient égaux. De la spécification de remove(), remove(o) supprime l'objet e de telle sorte que (o==null ? e==null : o.equals(e)) Est true. Notez que rien ne requiert que o et e soient du même type. Cela découle du fait que la méthode equals() prend un paramètre Object, pas seulement le même type que l'objet.

Bien qu'il soit généralement vrai que de nombreuses classes ont défini equals() de sorte que ses objets ne peuvent être égaux qu'aux objets de sa propre classe, ce n'est certainement pas toujours le cas. Par exemple, la spécification de List.equals() indique que deux objets List sont égaux s'ils sont tous deux Lists et ont le même contenu, même s'il s'agit d'implémentations différentes de List. Donc, pour revenir à l'exemple de cette question, il est possible d'avoir un Map<ArrayList, Something> Et pour moi d'appeler remove() avec un LinkedList comme argument, et cela devrait supprimer le clé qui est une liste avec le même contenu. Cela ne serait pas possible si remove() était générique et restreignait son type d'argument.

73
newacct

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.

Je me souviens avoir rencontré cette question avec la méthode get (Object) de Map. Dans ce cas, la méthode get 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 passez dans Maps avec un caractère générique comme premier paramètre de type, il n'y a aucun moyen de retirer 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 add est générique est que vous êtes censé 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 de toute façon à rien. Si l'argument était un caractère générique, la méthode serait simplement inutilisable, même si vous pouvez avoir un objet que vous pouvez GARANTIR appartient à cette collection, car vous venez de recevoir une référence à lui dans la ligne précédente ....

Je ne l'ai probablement pas très bien expliqué, mais cela me semble assez logique.

11
Bob Gettys

En plus des autres réponses, il existe une autre raison pour laquelle la méthode devrait accepter un Object, qui est des prédicats. Prenons l'exemple suivant:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

Le fait est que l'objet transmis à la méthode remove est responsable de la définition de la méthode equals. La construction de prédicats devient très simple de cette façon.

6
Hosam Aly

Supposons que l'on ait une collection de Cat et quelques références d'objet de types Animal, Cat, SiameseCat et Dog. Demander à la collection si elle contient l'objet référencé par la référence Cat ou SiameseCat semble raisonnable. Demander s'il contient l'objet référencé par la référence Animal peut sembler douteux, mais c'est toujours parfaitement raisonnable. L'objet en question peut, après tout, être un Cat, et peut 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 en quelque sorte pas significative (elle répondra toujours "non"). Néanmoins, puisqu'il n'y a aucun moyen de restreindre les paramètres à des sous-types ou des supertypes, il est plus pratique d'accepter simplement tout type et de répondre "non" pour tous les objets dont le type n'est pas lié à celui de la collection.

5
supercat

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, malgré tout, 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.

4
ColinD

C'était un compromis. Les deux approches ont leur avantage:

  • remove(Object o)
    • est plus flexible. Par exemple, il permet de parcourir une liste de nombres et de les supprimer d'une liste de longs.
    • le code qui utilise cette flexibilité peut être plus facilement généré
  • 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 métrages.

La compatibilité descendante a toujours été un objectif majeur lors de l'évolution de l'API Java, donc la suppression de (Object o) a été choisie car elle facilitait la génération de code existant. Si la compatibilité descendante n'avait PAS été un problème, je suis deviner que les concepteurs auraient choisi de supprimer (E e).

0
Stefan Feuerhahn