web-dev-qa-db-fra.com

Quelle est la répartition de la syntaxe Java lambda?

Veuillez expliquer la syntaxe des méthodes lambda de Java 8.

Il existe de nombreuses explications pour les fonctions what lambda, mais je ne trouve pas d'explication détaillée de la syntaxe, et il m'est très difficile d'apprendre à reproduire correctement la syntaxe car je ne 'comprend pas pourquoi ils sont écrits comme ils sont.

Voici un cas courant que je rencontre, avec la permission de NetBeans:

public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        new MainAppJFrame();
    });
}

Donc, d’une manière ou d’une autre, l’expression lambda suivante se résout en une méthode run () d’un objet Runnable anonyme:

() -> {
    // do stuff
}

Le -> est la syntaxe lambda appropriée, non? Et les accolades contiennent simplement le code de méthode anonyme. Les parenthèses sont-elles un argument vide, car dans ce cas, nous créons une méthode Runnable.run()?

Tout cela n’est pas clair pour moi. Je suppose que le compilateur sait instancier une Runnable anonyme en fonction du type attendu par la méthode SwingUtilities.invokeLater(Runnable)? Que se passerait-il s'il existait deux méthodes SwingUtilities.invokeLater ne différant que par la liste de paramètres? Évidemment il n'y en a pas dans ce cas précis, mais c'est possible ailleurs:

interface ExampleLambdaConsumer {
    public void doSomething(Runnable r);
    public void doSomething(Java.lang.reflect.Method m);
}

class C implements ExampleLambdaConsumer {
    // implementations for doSomething methods here

    public static void main(String[] args) {
        doSomething(() -> {
            // lambda method body here
        }
    }
}
27
muffin

La syntaxe est la suivante:

arguments -> body

arguments peut être soit

  • ()

  • une seule variable si le type de cette variable peut être déduit du contexte

  • une séquence de variables, avec ou sans types, entre parenthèses

et body peut être une expression ou un bloc {...} avec des instructions. L’expression est simplement renvoyée, c.-à-d. () -> 2 est équivalent à () -> {return 2;}


EDIT: En cas d’expressions lambda telles que () -> f():

  • si f() renvoie void, ils sont équivalents à () -> { f(); }

  • sinon, ils sont équivalents à () -> { f(); } ou () -> { return f(); }). Le compilateur le déduit du contexte de l'appel, mais il préfère généralement ce dernier.

Par conséquent, si vous avez deux méthodes: void handle(Supplier<T>) et void handle(Runnable), alors:

  • handle(() -> { return f(); }) et handle(() -> x) appellent le premier,

  • handle(() -> { f(); } appellera le second et

  • handle(() -> f()):

    • si f() renvoie void ou un type qui n'est pas convertible en T, le second sera appelé

    • si f() renvoie un type convertible en T, le premier sera appelé


Le compilateur essaie de faire correspondre le type de lambda au contexte. Je ne connais pas les règles exactes, mais la réponse à:

Que se passerait-il s'il y avait deux méthodes SwingUtilities.invokeLater qui diffèrent uniquement par la liste de paramètres?

est: cela dépend de ce que seraient ces listes de paramètres. Si l'autre invokeLater avait aussi exactement un paramètre et que ce paramètre serait de type qui est également une interface avec une méthode de type void*(), eh bien, alors il se plaindrait de ne pas pouvoir déterminer quelle méthode vous voulez dire.

Pourquoi sont-ils écrits comme ils sont? Eh bien, je pense que c'est parce que la syntaxe en C # et Scala est presque identique (ils utilisent => plutôt que ->).

34
Karol S

La syntaxe est

(parameter_list_here) -> { stuff_to_do; }

Les accolades peuvent être omises s'il s'agit d'une seule expression. Les parenthèses habituelles autour de la liste de paramètres peuvent être omises s'il s'agit d'un paramètre unique.

La syntaxe ne fonctionne que pour toutes les interfaces fonctionnelles. L'annotation @FunctionalInterface indique au compilateur que vous avez l'intention d'écrire une telle interface et génère une erreur de compilation si vous ne remplissez pas les conditions requises - par exemple, elle ne doit avoir qu'une seule méthode pouvant être remplacée.

@FunctionalInterface
interface TestInterface {
    void dostuff();
}

Runnable est aussi déclaré comme ça. Les autres interfaces ne le sont pas et ne peuvent pas être utilisées avec les fonctions lambda.

Maintenant que nous avons créé une nouvelle interface fonctionnelle avec une méthode ne prenant aucun paramètre, pourquoi ne pas tester la question que vous aviez sur la "collision" dans les signatures?

public class Main {
    private void test(Runnable r) {

    }
    private void test(TestInterface ti) {

    }
    public static void main(String[] args) { 
        test(() -> { System.out.println("test");})
    }

    @FunctionalInterface
    interface TestInterface {
        void dostuff();
    }
}

Résultat: erreur de compilation: appel ambigu à la méthode test.

Vous voyez, le compilateur/VM (si cela est fait à l'exécution) trouve les méthodes appropriées et leur liste de paramètres et voit si le paramètre est une interface fonctionnelle et s'il crée une implémentation anonyme de cette interface. Techniquement (en octet), il diffère d'une classe anonyme, mais reste identique (vous ne verrez pas les fichiers Main $ 1.class).

Votre exemple de code (avec la permission de Netbeans) peut également être remplacé par

SwingUtilities.invokeLater(MainAppJFrame::new);

Btw. :)

10
Xabster

Les expressions Lambda sont fondamentalement adoptées dans Java 8 pour simplifier la fonction de traitement prioritaire en tant que fonctions anonymes .

Ce ne sont que des raccourcis vers Ignorer les anciennes fonctions anonymes Java.

Reportez-vous à l'exemple suivant:

Supposons que vous avez l'interface A qui n'a qu'une méthode déclarée comme ci-dessous:

interface A{        
    void print();           
}

maintenant avec old Java style nous allons remplacer de manière anonyme comme ci-dessous:

new A() {           
        @Override
        public void print() {
            System.out.println("in a print method");                
        }           
};

de plus, maintenant, avec Java 8 lambda expression, nous l'utilisons comme ci-dessous:

() -> System.out.println("in a print method");

Ici, nous pouvons passer les paramètres requis à method avant l'opérateur ->, puis au remplacement du corps après l'opérateur ->.

le seul paramètre supplémentaire dont nous avons besoin pour y parvenir est que nous devons déclarer une interface avec @FunctionalInterface comme ci-dessous:

 @FunctionalInterface
 interface A{        
    void print();           
 }

Remarque: - Une expression lambda ne peut être utilisée que pour une interface "fonctionnelle" comportant une seule méthode autre que celle par défaut.

8
Pra Jazz

La syntaxe est déroutante. Cela me conduit autour de la torsion. Si ce n'était pas pour Intellij, corrigez-moi, je me tromperais.

Compile bien.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return (integer%2 == 0);};
}

}

Compile bien.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return integer%2 == 0;};
}

}

Ne compile pas, puisqu'il ne s'agit plus d'une déclaration.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return integer%2 == 0};
}

}

Ne compile pas, puisqu'il ne s'agit plus d'une instruction et que l'instruction return a été supprimée.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {integer%2 == 0};
}

}

Compile bien.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> integer%2 == 0;
}

}

Donc morale de l'histoire, si vous utilisez les crochets, ce qui est à l'intérieur doit être une déclaration et il doit renvoyer le type attendu par la méthode abstraite unique de l'interface fonctionnelle (c'est-à-dire la seule qui ne soit pas une valeur par défaut).

En fait, je l'ai écrit plus pour moi-même qu'autre chose, parce que ça me dérangeait.

0
Beezer