web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas utiliser des méthodes par défaut dans les expressions lambda?

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.

41
codegasmer

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 et super 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 via this), 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.

26

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); 
    }
};
12
Paul Boddington

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.

6
zapl

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.

1
Edwin Dalorzo