java lambda retournant un lambda

J’essaie de faire ce qui semble être une chose relativement simple dans le nouveau domaine de functional programming jdk8, mais je n’arrive pas à le faire fonctionner. J’ai ce code de travail:

import java.util.*; import java.util.concurrent.*; import java.util.stream.*; public class so1 { public static void main() { List l = new ArrayList(Arrays.asList(1, 2, 3)); List<Callable> checks = l.stream(). map(n -> (Callable) () -> { System.out.println(n); return null; }). collect(Collectors.toList()); } } 

Il prend une liste de nombres et produit une liste de fonctions pouvant les imprimer. Cependant, la dissortingbution explicite à Callable semble redondante. Il me semble et à IntelliJ. Et nous convenons tous les deux que cela devrait également fonctionner:

 List<Callable> checks = l.stream(). map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 

Cependant je reçois une erreur:

 so1.java:10: error: incompatible types: cannot infer type-variable(s) R List<Callable> checks = l.stream().map(n -> () -> {System.out.println(n); return null;}).collect(Collectors.toList()); ^ (argument mismatch; bad return type in lambda expression Object is not a functional interface) where R,T are type-variables: R extends Object declared in method map(Function) T extends Object declared in interface Stream 1 error 

Vous atteignez une limitation du typage cible de Java 8 qui s’applique au destinataire d’un appel de méthode. Bien que le typage cible fonctionne (la plupart du temps) pour les types de paramètre, il ne fonctionne pas pour l’object ou l’expression sur lequel vous appelez la méthode.

Ici, l.stream(). map(n -> () -> { System.out.println(n); return null; }) l.stream(). map(n -> () -> { System.out.println(n); return null; }) est le destinataire de l’invocation de la méthode collect(Collectors.toList()) , donc le type de cible List> n’est pas pris en compte pour cela.

Il est facile de prouver que les expressions lambda nestedes fonctionnent si le type de cible est connu, par exemple

 static  Function> toCallable() { return n -> () -> { System.out.println(n); return null; }; } 

fonctionne sans problème et vous pouvez l’utiliser pour résoudre votre problème d’origine

 List> checks = l.stream() .map(toCallable()).collect(Collectors.toList()); 

Vous pouvez également résoudre le problème en introduisant une méthode d’assistance qui modifie le rôle de la première expression du récepteur de méthode en un paramètre.

 // turns the Stream s from receiver to a parameter static  R collect(Stream s, Collector collector) { return s.collect(collector); } 

et réécrire l’expression originale comme

 List> checks = collect(l.stream().map( n -> () -> { System.out.println(n); return null; }), Collectors.toList()); 

Cela ne réduit pas la complexité du code mais peut être compilé sans problème. Pour moi, c’est un déjà vu. À la sortie de Java 5 et de Generics, les programmeurs devaient répéter les parameters de type sur les new expressions, tandis que le simple fait d’envelopper l’expression dans une méthode générique prouvait que déduire le type ne posait aucun problème. Il a fallu attendre Java 7 pour que les programmeurs soient autorisés à omettre ces répétitions inutiles des arguments de type (à l’aide de «l’opérateur Diamond»). Maintenant, nous avons une situation similaire, envelopper une expression d’invocation dans une autre méthode, transformant le récepteur en paramètre, prouve que cette limitation est inutile. Alors peut-être que nous nous débarrassons de cette limitation dans Java 10…

J’ai rencontré le même problème et j’ai pu le résoudre en spécifiant explicitement le paramètre de type générique à map comme suit:

 List> checks = l.stream(). >map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 

Je ne me suis pas encore penché sur les règles exactes du fonctionnement de l’inférence de type avec lambdas. Du sharepoint vue de la conception du langage en général, il n’est pas toujours possible d’écrire des règles de langage permettant au compilateur de trouver tout ce que nous pensons devoir. J’ai été responsable de la maintenance d’un compilateur en langage Ada et je connais de nombreux problèmes de conception de langage. Ada utilise l’inférence de type dans beaucoup de cas (où le type d’une construction ne peut pas être déterminé sans regarder l’expression entière contenant la construction, ce qui est le cas avec cette expression lambda Java également). Certaines règles linguistiques poussent les compilateurs à rejeter certaines expressions comme étant ambiguës, alors qu’en théorie, une seule interprétation est possible. Une des raisons, si je me souviens bien, est que quelqu’un a trouvé un cas où une règle qui laisserait le compilateur comprendre l’interprétation correcte aurait obligé le compilateur à faire 17 passages d’une expression pour l’interpréter correctement.

Ainsi, bien que nous puissions penser qu’un compilateur “devrait” être capable de comprendre quelque chose dans un cas particulier, cela peut tout simplement être irréalisable.