Lambdas et interfaces fonctionnelles
Depuis Java 8, les lambdas permettent d'écrire des fonctions anonymes de façon concise. Elles s'appuient sur les interfaces fonctionnelles : des interfaces avec une seule méthode abstraite.
Notions théoriques
Interface fonctionnelle
Une interface fonctionnelle est une interface qui ne contient qu'une seule méthode abstraite. L'annotation @FunctionalInterface est optionnelle mais recommandée : elle demande au compilateur de vérifier que la contrainte est respectée.
@FunctionalInterface
public interface Operation {
int calculer(int a, int b);
}
Lambdas — syntaxe de base
Une lambda remplace une classe anonyme par une expression concise :
// Ancienne syntaxe (classe anonyme)
Operation addition = new Operation() {
@Override
public int calculer(int a, int b) {
return a + b;
}
};
// Lambda équivalente
Operation addition = (a, b) -> a + b;
Syntaxes possibles selon le nombre de paramètres :
// Aucun paramètre
Runnable r = () -> System.out.println("Bonjour !");
// Un paramètre (parenthèses optionnelles)
Consumer<String> afficher = s -> System.out.println(s);
// Plusieurs paramètres
Comparator<String> comp = (s1, s2) -> s1.compareTo(s2);
// Bloc de plusieurs instructions
Function<Integer, String> descr = (n) -> {
if (n > 0) return "positif";
return "négatif ou nul";
};
Interfaces fonctionnelles standard
Java fournit des interfaces fonctionnelles prêtes à l'emploi dans java.util.function :
| Interface | Signature | Rôle |
|---|---|---|
Function<T, R> | R apply(T t) | Transformer T en R |
Predicate<T> | boolean test(T t) | Tester une condition |
Consumer<T> | void accept(T t) | Consommer T (effet de bord) |
Supplier<T> | T get() | Fournir une valeur T |
Function<String, Integer> longueur = s -> s.length();
Predicate<Integer> estPositif = n -> n > 0;
Consumer<String> afficher = s -> System.out.println(s);
Supplier<String> salut = () -> "Bonjour !";
System.out.println(longueur.apply("Java")); // 4
System.out.println(estPositif.test(-3)); // false
afficher.accept("Test"); // Test
System.out.println(salut.get()); // Bonjour !
Références de méthode
Quand la lambda ne fait qu'appeler une méthode existante, on peut utiliser une référence de méthode (notation ::) :
// Lambda // Référence de méthode équivalente
s -> s.toUpperCase() String::toUpperCase
s -> System.out.println(s) System.out::println
s -> new Personnage(s, 100, 10) Personnage::new // référence de constructeur
Exemple concret :
List<String> noms = List.of("aragorn", "gandalf", "legolas");
// Avec lambda
noms.forEach(s -> System.out.println(s));
// Avec référence de méthode
noms.forEach(System.out::println);
Exemple pratique
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
ArrayList<Personnage> personnages = new ArrayList<>();
personnages.add(new Guerrier("Aragorn", 150, 30));
personnages.add(new Mage("Gandalf", 100, 20, 50));
personnages.add(new Guerrier("Boromir", 130, 28));
personnages.add(new Mage("Saruman", 90, 25, 70));
// Predicate : filtrer les guerriers
Predicate<Personnage> estGuerrier = p -> p instanceof Guerrier;
System.out.println("=== Guerriers ===");
for (Personnage p : personnages) {
if (estGuerrier.test(p)) {
p.afficher();
}
}
// Function : obtenir la description d'un personnage
Function<Personnage, String> description = p -> p.getNom() + " (force=" + p.getForce() + ")";
System.out.println("\n=== Descriptions ===");
for (Personnage p : personnages) {
System.out.println(description.apply(p));
}
// Tri par force avec lambda Comparator
personnages.sort((p1, p2) -> p1.getForce() - p2.getForce());
System.out.println("\n=== Ordre par force croissante ===");
personnages.forEach(p -> p.afficher());
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez remplacer des classes anonymes par des lambdas, et trier une liste de personnages avec un Comparator lambda.
Étape 1 — Créer une interface fonctionnelle personnalisée
Créez l'interface @FunctionalInterface nommée ActionPersonnage avec la méthode executer(Personnage p).
L'annotation @FunctionalInterface est facultative mais fortement recommandée. Elle documente votre intention et protège contre les ajouts accidentels de méthodes abstraites qui casseraient toutes les lambdas existantes.
Étape 2 — Utiliser ActionPersonnage avec une lambda
Créez une variable ActionPersonnage soignerAction qui appelle p.afficher() sur le personnage passé, et utilisez-la dans une boucle.
Quand la lambda se résume à appeler une seule méthode existante (p -> p.afficher()), utilisez une référence de méthode : Personnage::afficher. C'est plus concis et supprime le paramètre redondant p.
Étape 3 — Trier une liste avec un Comparator lambda
Triez la liste de personnages par nom alphabétique (ordre croissant) en utilisant une lambda.
Pour trier des objets par un attribut String, Comparator.comparing(Personnage::getNom) est encore plus lisible que (p1, p2) -> p1.getNom().compareTo(p2.getNom()). Les deux sont corrects, mais la version avec référence de méthode exprime mieux l'intention.