web-dev-qa-db-fra.com

Pourquoi l'utilisation de Collections.emptySet () avec des génériques fonctionne-t-elle en affectation mais pas comme paramètre de méthode?

Donc, j'ai une classe avec un constructeur comme celui-ci:

public FilterList(Set<Integer> labels) {
    ...
}

et je veux construire un nouvel objet FilterList avec un ensemble vide. Suivant les conseils de Joshua Bloch dans son livre Effective Java, je ne veux pas créer un nouvel objet pour l'ensemble vide; Je vais simplement utiliser Collections.emptySet() à la place:

FilterList emptyList = new FilterList(Collections.emptySet());

Cela me donne une erreur, se plaignant que Java.util.Set<Java.lang.Object> N'est pas un Java.util.Set<Java.lang.Integer>. OK, qu'en est-il:

FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());

Cela me donne aussi une erreur! Ok, que diriez-vous de ceci:

Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);

Hé, ça marche! Mais pourquoi? Après tout, Java n'a pas d'inférence de type, c'est pourquoi vous obtenez un avertissement de conversion non contrôlé si vous faites Set<Integer> foo = new TreeSet() au lieu de Set<Integer> foo = new TreeSet<Integer>(). Mais Set<Integer> empty = Collections.emptySet(); fonctionne sans même un avertissement. Pourquoi?

54
Karl von L

La réponse courte est - c'est une limitation de l'inférence de type dans le système générique de Java. Il peut déduire des types génériques par rapport à des variables concrètes, mais pas par rapport aux paramètres de méthode.

Je suspecte c'est parce que les méthodes sont distribuées dynamiquement en fonction de la classe d'exécution de l'objet propriétaire, donc au moment de la compilation (quand all = les informations génériques sont résolues), vous ne pouvez pas vraiment savoir avec certitude quelle sera la classe du paramètre de méthode et ne pouvez donc pas en déduire. Les déclarations de variables sont agréables et constantes, vous pouvez donc.

Quelqu'un d'autre pourrait être en mesure de donner plus de détails et/ou un lien Nice. :-)

Dans tous les cas, vous pouvez toujours spécifier explicitement les paramètres de type pour les appels génériques comme ceci:

Collections.<Integer>emptySet();

ou même plusieurs paramètres à la fois, par ex.

Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>

Cela semble souvent un peu plus propre que d'avoir à lancer, dans les cas où l'inférence ne se déclenche pas.

118
Andrzej Doyle

essayer

FilterList emptyList = new FilterList(Collections.<Integer>emptySet());

Vous pouvez forcer le paramètre type pour les méthodes qui en disposent, dans les cas où l'inférence n'est pas assez bonne, ou pour vous permettre d'utiliser des sous-types; par exemple:

// forces use of ArrayList as parameter instead of the infered List
List<String> l = someObject.<ArrayList<String> methodThatTakesTypeParamForReturnType();
7

Vous voulez faire ceci:

FilterList emptyList = new FilterList(Java.util.Collections.<Integer>emptySet());

Cela indique à la méthode emptySet que son paramètre générique doit explicitement être Integer au lieu de la valeur par défaut Object. Et oui, la syntaxe est complètement funky et non intuitive pour cela. :)

6
jdmichal

Java a une inférence de type, c'est juste assez limité. Si vous souhaitez savoir exactement comment cela fonctionne et quelles sont ses limites, voici une très bonne lecture:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#Type%2BArgument%2BInference

5
kasperjj