De façon surprenante, le code suivant se compile avec succès:
Consumer<String> p = ""::equals;
Cela aussi:
p = s -> "".equals(s);
Mais cela échoue avec l'erreur boolean cannot be converted to void
comme prévu:
p = s -> true;
La modification du deuxième exemple avec des parenthèses échoue également:
p = s -> ("".equals(s));
Est-ce un bogue dans le compilateur Java ou existe-t-il une règle d'inférence de type que je ne connais pas?
Tout d'abord, il vaut la peine de regarder ce qu'est réellement un Consumer<String>
. De la documentation :
Représente une opération qui accepte un seul argument d'entrée et ne renvoie aucun résultat . Contrairement à la plupart des autres interfaces fonctionnelles, Consumer devrait fonctionner via des effets secondaires.
C'est donc une fonction qui accepte une chaîne et ne renvoie rien.
Consumer<String> p = ""::equals;
Compile avec succès car equals
peut prendre une chaîne (et, en fait, n'importe quel objet). Le résultat égal est simplement ignoré. *
p = s -> "".equals(s);
C'est exactement la même chose, mais avec une syntaxe différente. Le compilateur ne sait pas ajouter un return
implicite car un Consumer
ne doit pas renvoyer de valeur. Il le ferait ajouterait un return
implicite si le lambda était un Function<String, Boolean>
.
p = s -> true;
Cela prend une chaîne (s
) mais parce que true
est une expression et non une instruction, le résultat ne peut pas être ignoré de la même manière. Le compilateur doit ajouter un return
implicite car une expression ne peut pas exister seule. Ainsi, ce le fait a un retour: un booléen. Ce n'est donc pas un Consumer
. **
p = s -> ("".equals(s));
Encore une fois, c'est une expression, pas une déclaration. Ignorant les lambdas pendant un moment, vous verrez que la ligne System.out.println("Hello");
échouera de la même manière à être compilée si vous la mettez entre parenthèses.
* De la spécification :
Si le corps d'un lambda est une expression d'instruction (c'est-à-dire une expression qui serait autorisée à être autonome en tant qu'instruction), elle est compatible avec un type de fonction produisant un vide; tout résultat est simplement rejeté.
** De la spécification (merci, Eugene ):
Une expression lambda est congruente avec un type de fonction [producteur de vides] si ... le corps lambda est soit une expression ( §14.8 ) soit un bloc compatible avec les vides.
Je pense que les autres réponses compliquent l'explication en se concentrant sur les lambdas alors que leur comportement dans ce cas est similaire au comportement des méthodes implémentées manuellement. Cela compile:
new Consumer<String>() {
@Override
public void accept(final String s) {
"".equals(s);
}
}
alors que cela ne:
new Consumer<String>() {
@Override
public void accept(final String s) {
true;
}
}
car "".equals(s)
est une instruction mais true
ne l'est pas. Une expression lambda pour une interface fonctionnelle retournant void nécessite une instruction donc elle suit les mêmes règles que le corps d'une méthode.
Notez qu'en général les corps lambda ne suivent pas exactement les mêmes règles que les corps de méthode - en particulier, si un lambda dont le corps est une expression implémente une méthode renvoyant une valeur, il a un return
. Ainsi, par exemple, x -> true
Serait une implémentation valide de Function<Object, Boolean>
, Tandis que true;
N'est pas un corps de méthode valide. Mais dans ce cas particulier, les interfaces fonctionnelles et les corps de méthode coïncident.
s -> "".equals(s)
et
s -> true
ne vous fiez pas aux mêmes descripteurs de fonction.
s -> "".equals(s)
peut faire référence au descripteur de fonction String->void
ou String->boolean
.s -> true
Fait uniquement référence au descripteur de fonction String->boolean
.
Pourquoi ?
s -> "".equals(s)
, le corps du lambda: "".equals(s)
est une instruction qui produit une valeur.void
ou boolean
.Donc, en écrivant:
Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);
est valable.
Lorsque vous affectez le corps lambda à une variable déclarée Consumer<String>
, Le descripteur String->void
Est utilisé.
Bien sûr, ce code n'a pas beaucoup de sens (vous vérifiez l'égalité et vous n'utilisez pas le résultat) mais le compilateur s'en fiche.
C'est la même chose lorsque vous écrivez une instruction: myObject.getMyProperty()
où getMyProperty()
renvoie une valeur boolean
mais que vous ne stockez pas le résultat de il.
s -> true
, le corps du lambda: true
est une expression unique.boolean
.String->boolean
Peut être utilisé.Maintenant, revenez à votre code qui ne compile pas.
Qu'essayez-vous de faire ?
Consumer<String> p = s -> true;
Vous ne pouvez pas. Vous souhaitez affecter à une variable qui utilise le descripteur de fonction Consumer<String>
Un corps lambda avec le descripteur de fonction String->void
. Ça ne correspond pas!