Lambdas

Lambdas et functional interfaces

Introduites en java 8, les lambdas expressions sont des fonctions (des méthodes) anonymes qui sont traitées comme des valeurs par le langage.
La syntaxe est :
(paramètres) -> { instructions }
Par exemple
Runnable r = () -> System.out.println("Hello World");
Une lambda expression est forcémément associée à une interface qui ne contient qu'une seule méthode dont la signature (paramètres + types de retour) correspond à la lambda.
La lambda expression permet de fournir à la volée l'implémentation de cette méthode.
Dans l'exemple précédent, correspond à l'interface java.lang.Runnable.
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
L'équivalence peut être trouvée sans ambiguité grâce aux règles suivantes : Dans ce cas, une instance d'une classe implémentant l'interface attendue est créée, et le corps de la lambda est utilisé pour créer la méthode obligatoire.
La lambda expression est convertie. On utilise parfois le terme de single abstract method (or SAM) type pour se référer à cette interface.

Ces interfaces peuvent être signalées par l'annotation @FunctionalInterface.

Les lambdas peuvent donc être vues comme un raccourci syntaxique.
Runnable r = () -> System.out.println("Hello World");
est remplacé par le compilateur par :
Runnable r = new Runnable() {
    public void run() {
        System.out.println("Hello World");
    }
};
Pour faire afficher Hello World, on fait donc :
Runnable r = () -> System.out.println("Hello World");
r.run();
(code dans Run.java)

Règles syntaxiques des lambdas

Ce sont les mêmes règles utilisées pour les fonctions fléchées de javascript (voir developer.mozilla.org).

Exemple

public class LambdaDemo1{

    public static void main(String args[]) {
    
        MathOperation addition = (a, b) -> a + b; // syntaxe la plus concise
        MathOperation soustraction = (int a, int b) -> a - b;
        MathOperation multiplication = (int a, int b) -> { return a * b; };
        MathOperation division = new MathOperation(){
            public int operation(int a, int b){
                return a / b;
            }
        };
        
        // la ligne suivante ne passe pas à la compilation
        // int test = addition(3, 4);
        
        System.out.println("3 + 4 = " + addition.operation(3, 4));
        System.out.println("3 - 4 = " + soustraction.operation(3, 4));
        System.out.println("3 * 4 = " + multiplication.operation(3, 4));
        System.out.println("3 / 4 = " + division.operation(3, 4)); // attention, division entière
    }

    // on définit l'interface fonctionnelle comme une classe interne,
    // mais on aurait pu la définir à l'extérieur
    @FunctionalInterface
    private interface MathOperation {
        int operation(int a, int b);
    }
}
(code dans LambdaDemo1.java)
Exercice : Print Lambda

Retour sur FilenameFilter

On reprend l'exemple des classes anonymes en allant voir l'interface FilenameFilter :
package java.io;

@FunctionalInterface

public interface FilenameFilter{

    boolean accept(File dir, String name);

}
Avec les lambda expressions, cela peut s'exprimer par :
import java.io.File;
class Lambda3{
    public static void main(String[] args){
        File dir = new File(args[0]);
        String[] files = dir.list((f,s) -> { return s.endsWith(".java"); });
        for(String file : files){
            System.out.println(file);
        }
    }
}
Ces 2 codes sont équivalents :
String[] files =
    dir.list(
        new FilenameFilter() {
            public boolean accept(File f, String s) {
                return s.endsWith(".java");
            }
        }
    );
String[] files =
    dir.list(
        (f,s) -> {
            return s.endsWith(".java");
        }
    );

Method reference

Raccourci syntaxique lorsqu'il n'y a qu'un seul paramètre et que le corps de la lambda ne contient qu'une instruction contenant une seule méthode.

Permet d'écrire :
MyClass::toString
au lieu de :
(MyClass myObj) -> myObj.toString()

ou :
s -> System.out.println(s);
peut être exprimé par :
System.out::println

Généralités

Une motivation importante ayant conduit à l'introduction des lambdas en java est leur utilité dans les collections.

Java reste un langage objet mais est décrit par Oracle comme "légèrement fonctionnel" depuis l'introduction des lambdas.
Il n'existe pas de définition précise de ce qu'est un langage fonctionnel, mais doit au moins permettre de représenter une fonction comme une valeur qu'on peut mettre dans une variable.
Bien réaliser que ce n'est pas anodin et correspond à un mouvement qui touche un grand nombre de langages procéduraux (par ex php 5.3, C++ 11, go, javascript).

Noter aussi que dans un "vrai" un langage fonctionnel, le type fonction ~existe. Mais ce n'est pas le cas en java, une lambda expression étant finalement un raccourci syntaxique.