Le code Java Java échoue à la compilation:
@FunctionalInterface
private interface BiConsumer<A, B> {
void accept(A a, B b);
}
private static void takeBiConsumer(BiConsumer<String, String> bc) { }
public static void main(String[] args) {
takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
takeBiConsumer((String s1, String s2) -> "hi"); // Error
}
Le compilateur rapporte:
Error:(31, 58) Java: incompatible types: bad return type in lambda expression
Java.lang.String cannot be converted to void
La chose étrange est que la ligne marquée "OK" se compile bien, mais la ligne marquée "Erreur" échoue. Ils semblent essentiellement identiques.
Votre lambda doit correspondre à BiConsumer<String, String>
. Si vous vous référez à JLS # 15.27.3 (Type de Lambda) :
Une expression lambda est conforme à un type de fonction si toutes les conditions suivantes sont remplies:
- [...]
- Si le résultat du type de fonction est nul, le corps lambda est soit une expression d'instruction (§14.8), soit un bloc compatible avec le vide.
Donc, le lambda doit être soit une expression de déclaration, soit un bloc compatible avec le vide:
Fondamentalement, new String("hi")
est un morceau de code exécutable qui fait réellement quelque chose (il crée une nouvelle chaîne et la renvoie ensuite). La valeur renvoyée peut être ignorée et new String("hi")
peut toujours être utilisée dans lambda à retour nul pour créer une nouvelle chaîne.
Toutefois, "hi"
n'est qu'une constante qui ne fait rien par elle-même. La seule chose raisonnable à faire avec cela dans le corps lambda est de retourner le. Mais la méthode lambda devrait avoir le type de retour String
ou Object
, mais elle retourne void
, d'où le String cannot be casted to void
Erreur.
Le premier cas est correct car vous invoquez une méthode "spéciale" (un constructeur) et vous ne prenez pas réellement l'objet créé. Juste pour être plus clair, je vais mettre les accolades optionnelles dans vos lambdas:
takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error
Et plus clair, je traduirai cela dans l'ancienne notation:
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
new String("hi"); // OK
}
});
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
"hi"; // Here, the compiler will attempt to add a "return"
// keyword before the "hi", but then it will fail
// with "compiler error ... bla bla ...
// Java.lang.String cannot be converted to void"
}
});
Dans le premier cas, vous exécutez un constructeur, mais vous ne retournez PAS l'objet créé, dans le second cas, vous essayez de renvoyer une valeur String, mais votre méthode dans votre interface BiConsumer
renvoie void, d'où le compilateur Erreur.
Le JLS précise que
Si le résultat du type de fonction est nul, le corps lambda est soit une expression d'instruction (§14.8), soit un bloc compatible avec le vide.
Voyons maintenant cela en détail,
Puisque votre méthode takeBiConsumer
est de type void, le lambda recevant new String("hi")
l'interprétera comme un bloc comme
{
new String("hi");
}
qui est valide dans un vide, d'où la première compilation de cas.
Cependant, dans le cas où le lambda est -> "hi"
, un bloc tel que
{
"hi";
}
n'est pas une syntaxe valide en Java. Par conséquent, la seule chose à faire avec "hi" est d'essayer de le renvoyer.
{
return "hi";
}
qui n'est pas valide dans un vide et expliquer le message d'erreur
incompatible types: bad return type in lambda expression
Java.lang.String cannot be converted to void
Pour une meilleure compréhension, notez que si vous changez le type de takeBiConsumer
en String, -> "hi"
sera valide car il essaiera simplement de renvoyer directement la chaîne.
Notez qu'au début, je pensais que l'erreur était due au fait que le lambda était dans un mauvais contexte d'invocation, donc je partagerai cette possibilité avec la communauté:
Il s'agit d'une erreur au moment de la compilation si une expression lambda se produit dans un programme ailleurs que dans un contexte d'affectation (§5.2), un contexte d'invocation (§5.3) ou un contexte de transtypage (§5.5).
Cependant dans notre cas, nous sommes dans un contexte d'invocation qui est correct.