Pourquoi les consommateurs acceptent-ils les lambdas avec des corps de déclaration mais pas les corps d’expression?

Le code suivant est étonnamment compilé avec succès:

Consumer p = ""::equals; 

Cela aussi:

 p = s -> "".equals(s); 

Mais ceci é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 convient de regarder ce qu’est un Consumer . 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, le consommateur doit fonctionner via des effets secondaires.

C’est donc une fonction qui accepte une chaîne et ne renvoie rien.

 Consumer p = ""::equals; 

Comstack avec succès car equals peut prendre une chaîne (et, en fait, tout object). Le résultat des égaux 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 append un return implicite car un Consumer ne doit pas renvoyer de valeur. Cela appendait un return implicite si le lambda était une Function .

 p = s -> true; 

Cela prend un Ssortingng ( 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 append un return implicite car une expression ne peut exister seule. Ainsi, cela a un retour: un booléen. Par conséquent, ce n’est pas un Consumer . **

 p = s -> ("".equals(s)); 

Encore une fois, ceci est une expression , pas une déclaration. En ignorant les lambdas pendant un moment, vous verrez la ligne System.out.println("Hello"); De même, la compilation échouera si vous l’enveloppez entre parenthèses.


* De la spec :

Si le corps d’un lambda est une expression d’instruction (c’est-à-dire une expression qui serait autorisée à être autonome), elle est compatible avec un type de fonction produisant un vide; tout résultat est simplement supprimé.

** D’après les spécifications (merci, Eugene ):

Une expression lambda est conforme à un type de fonction [produisant un vide] si … le corps lambda est soit une expression d’instruction ( §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 comstack:

 new Consumer() { @Override public void accept(final Ssortingng s) { "".equals(s); } } 

alors que cela ne fait pas:

 new Consumer() { @Override public void accept(final Ssortingng s) { true; } } 

parce que "".equals(s) est une déclaration mais true ne l’est pas. Une expression lambda pour une interface fonctionnelle renvoyant une valeur vide nécessite une déclaration afin de suivre 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, celle-ci a un return implicite. Ainsi, par exemple, x -> true serait une implémentation valide de la Function , alors 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) peuvent faire référence à un descripteur de fonction Ssortingng->void ou Ssortingng->boolean .
s -> true réfère uniquement au descripteur de fonction Ssortingng->boolean .

Pourquoi ?

  • lorsque vous écrivez s -> "".equals(s) , le corps du lambda: "".equals(s) est une instruction qui produit une valeur .
    Le compilateur considère que la fonction peut renvoyer soit void soit boolean .

Donc écrire:

 Function function = s -> "".equals(s); Consumer consumer = s -> "".equals(s); 

est valable.

Lorsque vous affectez le corps lambda à une variable déclarée Consumer , le descripteur Ssortingng->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 moque.
C’est la même chose lorsque vous écrivez une déclaration: myObject.getMyProperty()getMyProperty() renvoie une valeur boolean mais que vous ne stockez pas le résultat.

  • Lorsque vous écrivez s -> true , le corps du lambda: true est une expression unique .
    Le compilateur considère que la fonction renvoie nécessairement une valeur boolean .
    Ainsi, seul le descripteur Ssortingng->boolean peut être utilisé.

Revenez maintenant à votre code qui ne comstack pas.
Qu’essayez-vous de faire ?

 Consumer p = s -> true; 

Vous ne pouvez pas. Vous voulez assigner à une variable qui utilise le descripteur de fonction Consumer un corps lambda avec le descripteur de fonction Ssortingng->void . Ça ne correspond pas!