Aller au contenu principal

Interfaces fonctionnelles

Une interface fonctionnelle est une interface qui ne contient qu'une seule méthode abstraite. Les interfaces fonctionnelles sont conçues pour être utilisées avec des expressions lambda ou des références de méthode. Elles permettent de définir un comportement sans avoir à créer des classes implémentant cette interface.

Une interface fonctionnelle est une interface qui contient une seule méthode abstraite. Voici un exemple simple avec une interface Square qui calcule le carré d'un nombre.

Exemple :

@FunctionalInterface // Non obligatoire mais permet de s'assurer que l'interface en est une.
interface Square {
int calculate(int x);
}

public class Test {
public static void main(String[] args) {
Square s = (int x) -> x * x; // Lambda qui calcule le carré
System.out.println(s.calculate(5)); // Affiche 25
}
}
  • Explication : L'interface Square contient une seule méthode abstraite calculate. On crée une lambda (int x) -> x * x qui correspond à cette méthode, et on l'utilise pour calculer le carré d'un nombre.

Built-In Functional Interfaces

Java 8 a introduit plusieurs interfaces fonctionnelles de ce type défini dans java.util.function. Elles sont couramment utilisées dans les opérations de flux (Streams) pour rendre le code plus concis et lisible.

https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/function/package-summary.html

Voici les principales:

Supplier

Supplier est une interface fonctionnelle qui génère un objet sans prendre de paramètres. Elle a une seule méthode : get(). C'est comme une "usine" qui fournit des objets.

Exemple :

Supplier<Double> randomValue = () -> {
Random r = new Random();
return r.nextDouble(); // Génère un nombre aléatoire entre 0 et 1
};

System.out.println(randomValue.get()); // Affiche un nombre aléatoire
System.out.println(randomValue.get());
System.out.println(randomValue.get());
  • Explication : La lambda () -> { return r.nextDouble(); } définit comment générer un nombre aléatoire. get() permet de récupérer cette valeur.

Consumer

Consumer est une interface fonctionnelle qui prend un paramètre et ne retourne rien. Elle est utilisée pour effectuer une action, comme afficher une donnée, écrire dans un fichier, ou envoyer des données sur un réseau.

Exemple :

import java.util.ArrayList;
import java.util.function.Consumer;

ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);

Consumer<Integer> printNumber = (n) -> System.out.println(n); // Consomme un entier et l'affiche
numbers.forEach(printNumber); // Affiche tous les nombres dans la liste
  • Explication : La lambda (n) -> System.out.println(n) est utilisée pour afficher chaque valeur de la liste.

Comparator

Comparator est une interface fonctionnelle qui permet de comparer des objets. Elle a une méthode compare(), qui retourne un entier indiquant si un objet est "plus grand", "plus petit" ou "égal" à un autre.

Exemple :

import java.util.*;

public class Employee {
int id;
String name;
double salary;

public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}

public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(115, "Charles", 1975000.00));
employees.add(new Employee(125, "Sylvain", 150000.00));
employees.add(new Employee(135, "Michel", 160000.00));

// Utilisation d'une lambda pour trier par salaire
employees.sort((e1, e2) -> Double.compare(e1.salary, e2.salary));

for (Employee e : employees) {
System.out.println(e.id + " " + e.name + " " + e.salary);
}
}
}
  • Explication : La lambda (e1, e2) -> Double.compare(e1.salary, e2.salary) permet de comparer les employés par salaire. Elle remplace une implémentation plus verbeuse d'un Comparator.

Predicate

Predicate est une interface fonctionnelle qui prend une valeur en paramètre et retourne un booléen (true ou false). Elle est souvent utilisée pour tester des conditions.

Exemple avec Predicate :

public static void main(String[] args) {
// Définir un Predicate qui vérifie si un nombre est pair
Predicate<Integer> isEven = number -> number % 2 == 0;

// Tester des valeurs
int[] numbers = {1, 2, 3, 4, 5, 6};
for (int number : numbers) {
if (isEven.test(number)) {
System.out.println(number + " est pair.");
} else {
System.out.println(number + " est impair.");
}
}
}
  • Explication : La lambda number -> number % 2 == 0 est utilisée pour filtrer les éléments non divisibles par 2 dans la liste.

On peut également mélanger plusieurs prédicats ensemble:

Predicate<Integer> isOdd = number -> number % 2 != 0;
Predicate<Integer> isPositive = number -> number > 0;

// Combinaison de prédicats
Predicate<Integer> isOddAndPositive = isOdd.and(isPositive);
System.out.println(isOddAndPositive.test(3)); // true
System.out.println(isOddAndPositive.test(-3)); // false

Pour créer des prédicats plus complexes.

L'interface Function

L'interface Function représente une fonction qui prend un paramètre et retourne une valeur. C'est une interface très utilisée avec les lambdas.

Exemple:

import java.util.function.Function;

public static void main(String[] args) {
Function<Long, Long> adder = (value) -> value + 3; // Lambda qui ajoute 3
Long result = adder.apply(4L);
System.out.println("Result: " + result); // Affiche 7
}

  • Explication : La lambda (value) -> value + 3 est une fonction qui prend un nombre et y ajoute 3.

Comme dans les cas précédents, on est toujours capable de chaîner les opérations:

Function<Integer, Integer> add5 = x -> x + 5;
Function<Integer, Integer> multiplyBy2 = x -> x * 2;

// Chaînage de fonctions avec `andThen()`
Function<Integer, Integer> add5ThenMultiply = add5.andThen(multiplyBy2);
System.out.println(add5ThenMultiply.apply(3)); // 16