Le code suivant est étonnamment compilé 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 bogue dans le compilateur Java ou y a-t-il une règle d'inférence de type que je ne connais pas?

62
Zefick 2 août 2017 à 15:27

2 réponses

Meilleure réponse

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 à égal est simplement ignoré. *

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

C'est exactement la même chose, mais avec une syntaxe différente. Le compilateur sait ne pas ajouter de return implicite car un Consumer ne doit pas renvoyer de valeur. Cela ajouterait un return implicite si le lambda était un Function<String, Boolean>.

p = s -> true;

Cela prend une chaîne (s) mais comme 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 par elle-même. Ainsi, ce a un retour: un booléen. Ce n'est donc 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 que la ligne System.out.println("Hello"); échouera de la même manière à se compiler si vous la mettez entre parenthèses.


* D'après 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), il 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 [produisant un vide] si ... le corps lambda est soit une expression d'instruction (§14.8) ou un bloc compatible avec le vide.

82
marc_s 16 janv. 2018 à 13:43

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 à celui 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;
    }
}

Parce que "".equals(s) est une instruction mais true ne l'est pas. Une expression lambda pour une interface fonctionnelle retournant void nécessite une instruction afin qu'elle suive 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 retournant une valeur, il a un {{X0 implicite }}. Ainsi, par exemple, x -> true serait une implémentation valide de Function<Object, Boolean>, 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éthodes coïncident.

12
Reinstate Monica 8 août 2017 à 14:14