Je lisais ce tutoriel sur Java 8 où l'auteur a montré le code:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Et puis dit
Les méthodes par défaut ne sont pas accessibles à partir d'expressions lambda. Le code suivant ne compile pas:
Formula formula = (a) -> sqrt( a * 100);
Mais il n'a pas expliqué pourquoi ce n'est pas possible. J'ai exécuté le code, et il a donné une erreur,
types incompatibles: la formule n'est pas une interface fonctionnelle "
Alors pourquoi n'est-ce pas possible ou quelle est la signification de l'erreur? L'interface satisfait à l'exigence d'une interface fonctionnelle ayant une méthode abstraite.
C'est plus ou moins une question de portée. Du JLS
Contrairement au code apparaissant dans les déclarations de classe anonymes, la signification des noms et des mots clés
this
etsuper
apparaissant dans un corps lambda, ainsi que l'accessibilité des déclarations référencées, sont les mêmes que dans le contexte environnant (sauf que les paramètres lambda introduisent de nouveaux noms).
Dans votre exemple
Formula formula = (a) -> sqrt( a * 100);
la portée ne contient pas de déclaration pour le nom sqrt
.
Ceci est également indiqué dans le JLS
Pratiquement parlant, il est inhabituel qu'une expression lambda ait besoin de parler d'elle-même (soit pour s'appeler récursivement, soit pour invoquer ses autres méthodes), alors qu'elle Il est plus courant de vouloir utiliser des noms pour faire référence à des éléments de la classe englobante qui seraient autrement masqués (
this
,toString()
). S'il est nécessaire qu'une expression lambda se réfère à elle-même (comme si viathis
), une référence de méthode ou une classe interne anonyme devrait être utilisée à la place.
Je pense qu'il aurait pu être mis en œuvre. Ils ont choisi de ne pas le permettre.
Les expressions lambda fonctionnent d'une manière complètement différente des classes anonymes dans la mesure où this
représente la même chose que dans la portée entourant l'expression.
Par exemple, cela compile
class Main {
public static void main(String[] args) {
new Main().foo();
}
void foo() {
System.out.println(this);
Runnable r = () -> {
System.out.println(this);
};
r.run();
}
}
et il imprime quelque chose comme
Main@f6f4d33
Main@f6f4d33
En d'autres termes, this
est un Main
, plutôt que l'objet créé par l'expression lambda.
Vous ne pouvez donc pas utiliser sqrt
dans votre expression lambda car le type de la référence this
n'est pas Formula
, ni un sous-type, et il n'a pas de sqrt
méthode.
Formula
est une interface fonctionnelle et le code
Formula f = a -> a;
compile et s'exécute pour moi sans aucun problème.
Bien que vous ne puissiez pas utiliser une expression lambda pour cela, vous pouvez le faire en utilisant une classe anonyme, comme ceci:
Formula f = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
Ce n'est pas tout à fait vrai. Les méthodes par défaut peuvent être utilisées dans les expressions lambda.
interface Value {
int get();
default int getDouble() {
return get() * 2;
}
}
public static void main(String[] args) {
List<Value> list = Arrays.asList(
() -> 1,
() -> 2
);
int maxDoubled = list.stream()
.mapToInt(val -> val.getDouble())
.max()
.orElse(0);
System.out.println(maxDoubled);
}
affiche 4
comme prévu et utilise une méthode par défaut dans une expression lambda (.mapToInt(val -> val.getDouble())
)
Ce que l'auteur de votre article essaie de faire ici
Formula formula = (a) -> sqrt( a * 100);
consiste à définir un Formula
, qui fonctionne comme interface fonctionnelle, directement via une expression lambda.
Cela fonctionne très bien, dans l'exemple de code ci-dessus, Value value = () -> 5
ou avec Formula
comme interface par exemple
Formula formula = (a) -> 2 * a * a + 1;
Mais
Formula formula = (a) -> sqrt( a * 100);
échoue car il essaie d'accéder à la méthode (this.
) sqrt
mais il ne peut pas. Les lambdas selon la spécification héritent de leur portée de leur environnement, ce qui signifie que this
à l'intérieur d'un lambda fait référence à la même chose que directement à l'extérieur de celui-ci. Et il n'y a pas de méthode sqrt
à l'extérieur.
Mon explication personnelle: à l'intérieur de l'expression lambda, on ne sait pas vraiment à quelle interface fonctionnelle concrète la lambda va être "convertie". Comparer
interface NotRunnable {
void notRun();
}
private final Runnable r = () -> {
System.out.println("Hello");
};
private final NotRunnable r2 = r::run;
La même expression lambda peut être "castée" en plusieurs types. J'y pense comme si une lambda n'avait pas de type. C'est une fonction spéciale sans type qui peut être utilisée pour n'importe quelle interface avec les bons paramètres. Mais cette restriction signifie que vous ne pouvez pas utiliser de méthodes du futur type car vous ne pouvez pas le savoir.
Cela ajoute peu à la discussion, mais je l'ai quand même trouvé intéressant.
Une autre façon de voir le problème serait d'y penser du point de vue d'un lambda auto-référencé.
Par exemple:
Formula formula = (a) -> formula.sqrt(a * 100);
Il semblerait que cela devrait avoir un sens, car au moment où le lambda doit être exécuté, la référence formula
doit avoir déjà été initialisée (c'est-à-dire qu'il n'y a pas moyen de faire formula.apply()
jusqu'à formula
a été correctement initialisé, auquel cas, à partir du corps du lambda, le corps de apply
, il devrait être possible de référencer la même variable).
Cependant, cela ne fonctionne pas non plus. Fait intéressant, cela était possible au début. Vous pouvez voir que Maurice Naftalin l'avait documenté dans son Lambda FAQ Site Web . Mais pour une raison quelconque, le support de cette fonctionnalité était finalement supprimé =.
Certaines des suggestions données dans d'autres réponses à cette question y ont déjà été mentionnées dans la discussion même de la liste de diffusion lambda.