Expression Lambda et méthode générique

111

Supposons que j'ai une interface générique:

interface MyComparable<T extends Comparable<T>>  {
    public int compare(T obj1, T obj2);
}

Et une méthode sort:

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable<T> comp) {
    // sort the list
}

Je peux invoquer cette méthode et passer une expression lambda comme argument:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

Cela fonctionnera très bien.

Mais maintenant, si je rends l'interface non générique et la méthode générique:

interface MyComparable {
    public <T extends Comparable<T>> int compare(T obj1, T obj2);
}

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable comp) {
}

Et puis invoquez ceci comme:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

Il ne compile pas. Il montre une erreur à l'expression lambda en disant:

"La méthode cible est générique"

OK, lorsque je l'ai compilé en utilisant javac, il affiche l'erreur suivante:

SO.java:20: error: incompatible types: cannot infer type-variable(s) T#1
        sort(list, (a, b) -> a.compareTo(b));
            ^
    (argument mismatch; invalid functional descriptor for lambda expression
      method <T#2>(T#2,T#2)int in interface MyComparable is generic)
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<T#1> declared in method <T#1>sort(List<T#1>,MyComparable)
    T#2 extends Comparable<T#2> declared in method <T#2>compare(T#2,T#2)
1 error

À partir de ce message d'erreur, il semble que le compilateur ne soit pas en mesure de déduire les arguments de type. Est-ce le cas? Si oui, pourquoi cela se passe-t-il ainsi?

J'ai essayé différentes méthodes, j'ai cherché sur Internet. Ensuite, j'ai trouvé cet article JavaCodeGeeks , qui montre un moyen, alors j'ai essayé:

sort(list, <T extends Comparable<T>>(a, b) -> a.compareTo(b));

qui encore une fois ne fonctionne pas, contrairement à ce que cet article prétend que cela fonctionne. Peut-être qu'il fonctionnait dans certaines versions initiales.

Ma question est donc la suivante: existe-t-il un moyen de créer une expression lambda pour une méthode générique? Je peux le faire en utilisant une référence de méthode, en créant une méthode:

public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
    return obj1.compareTo(obj2);
}

dans une classe, dites SOet passez-le comme:

sort(list, SO::compare);
Rohit Jain
la source

Réponses:

117

Vous ne pouvez pas utiliser une expression lambda pour une interface fonctionnelle , si la méthode de l' interface fonctionnelle a des paramètres de type . Voir la section §15.27.3 dans JLS8 :

Une expression lambda est compatible [..] avec un type cible T si T est un type d'interface fonctionnelle (§9.8) et l'expression est congruente avec le type de fonction de [..] T. [..] Une expression lambda est congruente avec un type de fonction si toutes les conditions suivantes sont remplies:

  • Le type de fonction n'a pas de paramètres de type .
  • [..]
nosid
la source
47
Cependant, cette restriction ne s'applique pas aux références de méthode aux méthodes génériques. Vous pouvez utiliser une référence de méthode à une méthode générique avec une interface fonctionnelle générique.
Brian Goetz
17
Je suis sûr qu'il y a une bonne raison à cette restriction. Qu'Est-ce que c'est?
Sandro du
6
@Sandro: il n'y a tout simplement pas de syntaxe pour déclarer les paramètres de type pour une expression lambda. Et une telle syntaxe serait très compliquée. Gardez à l'esprit que l'analyseur doit toujours être capable de distinguer une telle expression lambda avec des paramètres de type en dehors des autres constructions Java légales. Il faut donc recourir à des références de méthodes. La méthode cible peut déclarer des paramètres de type à l'aide d'une syntaxe établie.
Holger
2
@Holger quand même, où les paramètres de type peuvent être déduits automatiquement, le compilateur pourrait décoder un type capure comme il le fait lorsque vous déclarez par exemple Set <?> Et effectuez des vérifications de type avec ces types capturés. Bien sûr, cela rend impossible de les donner comme paramètres de type dans le corps, mais si vous en avez besoin, le recours à des références de méthode est une bonne alternative
WorldSEnder
17

En utilisant la référence de méthode, j'ai trouvé un autre moyen de transmettre l'argument:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
Andreï
la source
3

Indiquez simplement au compilateur la version appropriée du comparateur générique avec (Comparator<String>)

Donc la réponse sera

sort(list, (Comparator<String>)(a, b) -> a.compareTo(b));

Aleч
la source
2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparableet MyComparablen'est pas générique (pas de type) donc (MyComparable<String>)ne fonctionnerait pas non plus
user85421
1
Je ne sais pas comment vous tapez le code @CarlosHeuberger, mais cela fonctionne très bien pour moi, c'est ce que je cherchais.
Ivan Perales M.
@IvanPeralesM. après 2 mois ... j'ai copié et collé votre code et copié et collé au-dessus de la ligne sur votre ligne de tri - juste ça, comme ici: ideone.com/YNwBbF ! Êtes-vous sûr d'avoir saisi exactement le code ci-dessus? en utilisant Compartor?
user85421
Non je ne sais pas, j'utilise l'idée derrière la réponse, pour lancer la fonctionnalité pour dire à la compilation de quel type il s'agit et cela a fonctionné.
Ivan Perales M.
@IvanPeralesM. Eh bien, alors quel est votre problème avec ma "frappe"? La réponse, telle qu'elle est publiée, ne fonctionne pas.
user85421
0

Tu veux dire quelque chose comme ca?:

<T,S>(T t, S s)->...

De quel type est ce lambda? Vous ne pouvez pas exprimer cela en Java et ne pouvez donc pas composer cette expression dans une application de fonction et les expressions doivent être composables.

Pour que cela soit du travail, vous aurez besoin de la prise en charge des types Rank2 en Java.

Les méthodes sont autorisées à être génériques, mais vous ne pouvez donc pas les utiliser comme expressions. Ils peuvent cependant être réduits à l'expression lambda en spécialisant tous les types génériques nécessaires avant de pouvoir les transmettre:ClassName::<TypeName>methodName

icône
la source
1
"Of what type is this lambda? You couldn't express that in Java..."Le type serait déduit à l'aide du contexte, comme avec n'importe quel autre lambda. Le type d'un lambda n'est pas explicitement exprimé dans le lambda lui-même.
Kröw