62 votes

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

Le code suivant est étonnamment en train de compiler 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 entre parenthèses échoue également:

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

Est-ce un bug du compilateur Java ou existe-t-il une règle d'inférence de type que je ne connais pas?

83voto

Michael Points 20266

Tout d'abord, il est utile de regarder ce qu'est un Consumer<String> est en réalité. À partir de la documentation:

Représente une opération qui accepte un seul argument d'entrée et de renvoie aucun résultat. Contrairement à la plupart des autres interfaces fonctionnelles, de la Consommation est prévu pour fonctionner à partir d'effets secondaires.

C'est donc une fonction qui accepte une Chaîne de caractères et renvoie rien.

Consumer<String> p = ""::equals;

Compile correctement, car equals pouvez prendre une Corde (et, en effet, n'importe quel Objet). Le résultat d'égal à égal est tout simplement ignorés.*

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

C'est exactement le même, mais avec une syntaxe différente. Le compilateur ne sait pas à ajouter un implicite return car Consumer ne devrait pas renvoyer une valeur. Il serait ajouter un implicite return si le lambda est un Function<String, Boolean> si.

p = s -> true;

Cela prend une Chaîne de caractères (s), mais parce qu' true est une expression et non une déclaration, le résultat ne peut pas être ignoré de la même manière. Le compilateur doit ajouter un implicite return car une expression qui ne peuvent pas exister sur son propre. Ainsi, ce n'est avoir un retour: une valeur booléenne. Par conséquent, il n'est pas un Consumer.**

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

Encore une fois, c'est une expression, pas une déclaration. Ignorant les lambdas pour un moment, vous verrez la ligne System.out.println("Hello"); va de même ne parviennent pas à compiler si vous la mettez entre parenthèses.


*À partir de la spécification:

Si le corps d'un lambda est une déclaration de l'expression (c'est une expression qui aurait permis de rester seul comme un énoncé), il est compatible avec un vide-fonction de production de type; le résultat est tout simplement ignorée.

**À partir de la spécification (merci, Eugène):

Une expression lambda est en harmonie avec un [vide-production] type de fonction si ... le corps de lambda est une déclaration expression (§14.8) ou un vide-compatible bloc.

12voto

Je pense que les autres réponses compliquer l'explication en mettant l'accent sur les lambdas alors que leur comportement dans ce cas est similaire au comportement de manuellement mis en œuvre des méthodes. Cette compile:

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

alors que ce n'est pas:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        true;
    }
}

parce qu' "".equals(s) est un énoncé, mais true ne l'est pas. Une expression lambda pour une interface fonctionnelle de retour void nécessite une déclaration ainsi qu'il suit les mêmes règles que la méthode du corps.

Notez qu'en général lambda corps ne suivent pas exactement les mêmes règles que la méthode corps - en particulier, si un lambda dont le corps est une expression met en œuvre une méthode retournant une valeur, elle est implicite return. Ainsi, par exemple, x -> true serait valide la mise en œuvre de l' Function<Object, Boolean>, alors que true; n'est pas une méthode valable corps. Mais dans ce cas particulier d'interfaces fonctionnelles et le corps de méthode coïncident.

8voto

davidh Points 107
s -> "".equals(s)

et

s -> true

ne comptez pas sur la même fonction de descripteurs.

s -> "".equals(s) peuvent se référer soit String->void ou String->boolean fonction du descripteur.
s -> true ne se réfère qu' String->boolean fonction du descripteur.

Pourquoi ?

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

Ainsi écrit :

Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);

est valide.

Lorsque vous affectez le corps de lambda à un Consumer<String> variable déclarée, le descripteur String->void est utilisé.
Bien sûr, ce code n'a pas beaucoup de sens (vous vérifier l'égalité et que vous n'utilisez pas le résultat), mais le compilateur n'a pas de soins.
C'est la même chose lorsque vous écrivez une déclaration:" myObject.getMyProperty()getMyProperty() renvoie un boolean de la valeur, mais que vous n'avez pas stocker le résultat.

  • lorsque vous écrivez s -> true, le corps de la lambda : true est une expression unique .
    Le compilateur considère que la fonction renvoie nécessairement boolean.
    Si seulement le descripteur 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 la fonction descripteur Consumer<String> d'un corps de lambda, avec l' String->void fonction du descripteur. Il ne correspond pas aux !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X