Aller au contenu principal

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 :

InterfaceSignatureRô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


Combien de méthodes abstraites une interface fonctionnelle peut-elle avoir ?


Quelle est la syntaxe correcte d'une lambda sans paramètre ?


Quelle interface fonctionnelle retourne une valeur sans prendre de paramètre ?


Comment s'écrit la référence de méthode pour System.out.println ?


Quelle interface fonctionnelle teste une condition et retourne un boolean ?


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).


Bonne pratique - @FunctionalInterface pour documenter l'intention

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.


Bonne pratique - Lambda ou référence de méthode ?

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.


Bonne pratique - Comparator.comparing() pour les String

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.

📌 Une solution