Comparator.reversed () ne compile pas en utilisant lambda

111

J'ai une liste avec des objets utilisateur et j'essaie de trier la liste, mais ne fonctionne qu'en utilisant une référence de méthode, avec une expression lambda, le compilateur donne une erreur:

List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error

Erreur:

com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
            userList.sort(Comparator.comparing(u -> u.getName()).reversed());
                                                     ^
symbol:   method getName()
location: variable u of type Object
1 error
Andreï
la source

Réponses:

145

Il s'agit d'une faiblesse dans le mécanisme d'inférence de type du compilateur. Afin de déduire le type de udans le lambda, le type de cible pour le lambda doit être établi. Ceci est accompli comme suit. userList.sort()attend un argument de type Comparator<User>. Dans la première ligne, Comparator.comparing()doit revenir Comparator<User>. Cela implique qu'il Comparator.comparing()faut un Functionqui prend un Userargument. Ainsi, dans le lambda sur la première ligne, udoit être de type Useret tout fonctionne.

Dans les deuxième et troisième lignes, la saisie de la cible est perturbée par la présence de l'appel à reversed(). Je ne sais pas exactement pourquoi; le récepteur et le type de retour de reversed()sont Comparator<T>donc il semble que le type de cible devrait être propagé vers le récepteur, mais ce n'est pas le cas. (Comme je l'ai dit, c'est une faiblesse.)

Dans la deuxième ligne, la référence de méthode fournit des informations de type supplémentaires qui comblent cette lacune. Cette information est absente de la troisième ligne, donc le compilateur en déduit uêtre Object(le repli d'inférence de dernier recours), qui échoue.

Évidemment, si vous pouvez utiliser une référence de méthode, faites-le et cela fonctionnera. Parfois, vous ne pouvez pas utiliser une référence de méthode, par exemple, si vous souhaitez passer un paramètre supplémentaire, vous devez donc utiliser une expression lambda. Dans ce cas, vous fournirez un type de paramètre explicite dans le lambda:

userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());

Il est possible que le compilateur soit amélioré pour couvrir ce cas dans une version ultérieure.

Marques Stuart
la source
28
Les lambdas sont divisés en types implicitement (pas de types manifestes pour les paramètres) et explicitement typés ; les références de méthode sont divisées en exactes (pas de surcharge) et inexactes . Lorsqu'un appel de méthode générique dans une position de récepteur a des arguments lambda et que les paramètres de type ne peuvent pas être entièrement déduits des autres arguments, vous devez fournir un lambda explicite, une référence de méthode exacte, un cast de type cible ou des témoins de type explicite pour l'appel de méthode générique pour fournir les informations de type supplémentaires nécessaires pour continuer.
Brian Goetz
1
@StuartMarks, vous "ne savez pas exactement pourquoi" le compilateur agit ainsi. Mais que dit la spécification du langage ? Devrait-il y avoir suffisamment d'informations pour déterminer les types génériques, selon la spécification du langage? Si tel est le cas, il s'agit d'un bogue du compilateur et doit être classé et traité en conséquence. Sinon, c'est un domaine dans lequel le langage Java devrait être amélioré. Lequel est-ce?
Garret Wilson
8
Je pense que nous pouvons traiter les commentaires de Brian comme définitifs, car il a écrit la spécification en question :-)
minimalis
1
Malheureusement, rien de tout cela n'explique pourquoi il fonctionne sans inversé alors qu'il ne fonctionne pas avec inversé.
Chris311
90

Vous pouvez contourner cette limitation en utilisant l'argument Comparator.comparingà deux avec Comparator.reverseOrder()comme deuxième argument:

users.sort(comparing(User::getName, reverseOrder()));
Misha
la source
4
Agréable. J'aime mieux cela que d'utiliser un lambda explicitement typé. Ou, mieux encore, users.sort(reverseOrder(comparing(User::getName)));.
jouer
10
Notez que la reverseOrder(Comparator<T>)méthode ci-dessus est dans java.util.Collections, pas dans Comparator.
jouer