Supposons que vous avez un List
de nombres. Les valeurs dans List
peuvent être de type Integer
, Double
etc. Lorsque vous déclarez un tel List
, il est possible de le déclarer à l'aide d'un caractère générique ( ?
) Ou sans caractère générique.
final List<Number> numberList = Arrays.asList(1, 2, 3D);
final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D);
Donc, maintenant je veux stream
sur le List
et collect
tout cela à un Map
en utilisant le Collectors.toMap
(Évidemment le code ci-dessous est juste un exemple pour illustrer le problème). Commençons par diffuser le numberList
:
final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);
numberList.stream().collect(Collectors.toMap(
// Here I can invoke "number.intValue()" - the object ("number") is treated as a Number
number -> Integer.valueOf(number.intValue()),
number -> number));
Mais, je ne peux pas faire la même opération sur le wildcardList
:
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.toMap(
// Why is "number" treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number));
Le compilateur se plaint de l'appel à number.intValue()
avec le message suivant:
Test.Java: impossible de trouver le symbole
Symbole : méthode intValue ()
emplacement: nombre variable de type Java.lang.Object
D'après l'erreur du compilateur, il est évident que le number
dans le lambda est traité comme un Object
au lieu d'un Number
.
Donc, maintenant à ma (mes) question (s):
List
, pourquoi ne fonctionne-t-elle pas comme la version non générique du List
?number
dans le lambda est-elle considérée comme un Object
au lieu d'un Number
?C'est l'inférence de type qui ne fait pas les choses correctement. Si vous fournissez explicitement l'argument type, il fonctionne comme prévu:
List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.<Number, Integer, Number>toMap(
number -> Integer.valueOf(number.intValue()),
number -> number));
Il s'agit d'un bogue javac connu: l'inférence ne doit pas mapper les variables de capture à leurs limites supérieures . Le statut, selon Maurizio Cimadamore,
un correctif a été tenté puis annulé car il cassait des cas en 8, nous avons donc opté pour un correctif plus conservateur en 8 tout en faisant tout en 9
Apparemment, le correctif n'a pas encore été repoussé. (Merci à Joel Borggrén-Franck de m'avoir pointé dans la bonne direction.)
La déclaration du formulaire List<? extends Number> wildcardList
implique une "liste de type inconnu qui est Number
ou une sous-classe de Number
". Fait intéressant, le même type de liste avec un type inconnu fonctionne, si le type inconnu est référencé par un nom:
static <N extends Number> void doTheThingWithoutWildCards(List<N> numberList) {
numberList.stream().collect(Collectors.toMap(
// Here I can invoke "number.intValue()" - the object is treated as a Number
number -> number.intValue(),
number -> number));
}
Ici, N
est toujours "un type inconnu étant Number
ou une sous-classe de Number
" mais vous pouvez traiter le List<N>
comme prévu. Vous pouvez attribuer le List<? extends Number>
à un List<N>
sans problème comme contrainte que le type inconnu extends Number
est compatible.
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
doTheThingWithoutWildCards(wildCardList); // or:
doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D));
Le chapitre sur l'inférence de type n'est pas une lecture facile. Je ne sais pas s'il y a une différence entre les caractères génériques et les autres types à cet égard, mais je ne pense pas qu'il devrait y en avoir. Alors c'est soit un bug du compilateur ou une limitation par spécification mais logiquement, il n'y a aucune raison pour que le caractère générique ne fonctionne pas.
Cela est dû à inférence de type , dans le premier cas, vous avez déclaré List<Number>
Donc le compilateur n'a rien contre quand vous écrivez number -> Integer.valueOf(number.intValue())
parce que le type de variable number
est Java.lang.Number
Mais dans le deuxième cas, vous avez déclaré final List<? extends Number> wildCardList
En raison duquel Collectors.toMap
Est traduit en quelque chose comme Collectors.<Object, ?, Map<Object, Number>toMap
Par exemple.
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Object, ?, Map<Object, Object>> collector = Collectors.toMap(
// Why is number treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number);
wildCardList.stream().collect(collector);
À la suite de quoi dans l'expression
number -> Integer.valueOf(number.intValue()
le type de variable number
est Object et aucune méthode intValue()
n'est définie dans la classe Object. Par conséquent, vous obtenez une erreur de compilation.
Ce dont vous avez besoin est de passer des arguments de type collecteur qui aident le compilateur à résoudre l'erreur intValue()
Par exemple.
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Number, ?, Map<Integer, Number>> collector = Collectors.<Number, Integer, Number>toMap(
// Why is number treated as an Object and not a Number?
Number::intValue,
number -> number);
wildCardList.stream().collect(collector);
De plus, vous pouvez utiliser la référence de méthode Number::intValue
Au lieu de number -> Integer.valueOf(number.intValue())
Pour plus de détails sur l'inférence de type dans Java 8 veuillez vous référer ici .