web-dev-qa-db-fra.com

Java 8 Streams: pourquoi Collectors.toMap se comporte-t-il différemment pour les génériques avec des caractères génériques?

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):

  • Lors de la collecte de la version générique du List, pourquoi ne fonctionne-t-elle pas comme la version non générique du List?
  • Pourquoi la variable number dans le lambda est-elle considérée comme un Object au lieu d'un Number?
27
wassgren

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.)

35
aioobe

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.

3
Holger

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 .

2
sol4me