Cela fait un moment, mais Java 8 a apporté de nombreuses fonctionnalités intéressantes. L'une des plus révolutionnaires a été l'introduction des interfaces fonctionnelles. Si vous avez déjà été noyé sous les classes anonymes, les interfaces fonctionnelles sont là pour vous sauver ! Alors, plongeons dedans, amusons-nous et apprenons l'essentiel sur les interfaces fonctionnelles en Java 8.
Qu'est-ce qu'une Interface Fonctionnelle ?
Une interface fonctionnelle est une interface qui possède exactement une seule méthode abstraite. Imaginez-la comme cet ami minimaliste qui n'a besoin que d'une seule chose pour fonctionner correctement. Bien sûr, elle peut contenir plusieurs méthodes par défaut ou statiques, mais une seule méthode abstraite définit son comportement fonctionnel.
L'Annotation @FunctionalInterface
Java fournit l'annotation @FunctionalInterface pour garantir qu'une interface est bien fonctionnelle. Si vous ajoutez accidentellement une autre méthode abstraite, le compilateur renverra une erreur. Cette annotation est facultative mais recommandée, car elle indique clairement au compilateur :
"Hey, cette interface est censée être fonctionnelle !"
Exemple :
@FunctionalInterface
interface Greeting {
void sayHello();
}@FunctionalInterface
interface Greeting {
void sayHello();
}@FunctionalInterface
interface Greeting {
void sayHello();
}@FunctionalInterface
interface Greeting {
void sayHello();
}Une seule méthode—clair et simple !
Pourquoi est-ce important ?
Parce que les interfaces fonctionnelles permettent d'utiliser les expressions lambda, et les expressions lambda rendent votre code plus concis et lisible ! Sans elles, vous seriez coincé avec des classes internes anonymes, comme si vous étiez encore en 2005...
Avant Java 8 (L'Âge Sombre)
Si vous vouliez passer un comportement en argument, vous deviez créer une classe anonyme:
Greeting greeting = new Greeting() {
@override 1 usage
public voide sayHello(String name) {
System.out.println("Hello, "+name+"!");
}
};
greeting.sayHello(name: "Alice");Greeting greeting = new Greeting() {
@override 1 usage
public voide sayHello(String name) {
System.out.println("Hello, "+name+"!");
}
};
greeting.sayHello(name: "Alice");Greeting greeting = new Greeting() {
@override 1 usage
public voide sayHello(String name) {
System.out.println("Hello, "+name+"!");
}
};
greeting.sayHello(name: "Alice");Greeting greeting = new Greeting() {
@override 1 usage
public voide sayHello(String name) {
System.out.println("Hello, "+name+"!");
}
};
greeting.sayHello(name: "Alice");
Interfaces Fonctionnelles Prédéfinies en Java 8
Java propose dans le package java.util.function des interfaces fonctionnelles prêtes à l'emploi.
Voici les plus courantes :
1. Predicate<T> – Vérifie une Condition
Utilisé pour tester si une condition est vraie ou fausse.
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(10));
System.out.println(isEven.test(11));
}
}import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(10));
System.out.println(isEven.test(11));
}
}import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(10));
System.out.println(isEven.test(11));
}
}import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(10));
System.out.println(isEven.test(11));
}
}2. Consumer<T> – Consomme une Valeur sans Retour
Prend une entrée et exécute une action, mais ne retourne rien.
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printMessage = message -> System.out.println("Message: " + message);
printMessage.accept("Hello, Java 8!");
}
}import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printMessage = message -> System.out.println("Message: " + message);
printMessage.accept("Hello, Java 8!");
}
}import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printMessage = message -> System.out.println("Message: " + message);
printMessage.accept("Hello, Java 8!");
}
}import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printMessage = message -> System.out.println("Message: " + message);
printMessage.accept("Hello, Java 8!");
}
}3. Supplier<T> – Fournit une Valeur
Fournit une valeur à la demande, comme une machine à café, mais sans le stress existentiel.
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
}
}import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
}
}import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
}
}import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
}
}4. Function<T, R> – Transforme une Valeur
Prend une entrée et retourne une autre valeur (comme transformer un code en bugs en production… Juste une blague, enfin j’espère).
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Functional Interface"));
}
}import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Functional Interface"));
}
}import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Functional Interface"));
}
}import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Functional Interface"));
}
} 5. BiFunction<T, U, R> – Prend Deux Arguments et Retourne un Résultat
Parfois, un seul paramètre ne suffit pas. Utilisez BiFunction !
import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 10));
}
}import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 10));
}
}import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 10));
}
}import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 10));
}
} Créer ses Propres Interfaces Fonctionnelles
Si les interfaces prédéfinies ne suffisent pas, vous pouvez toujours créer la vôtre.
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
MathOperation addition = (a, b) -> a + b;
System.out.println(addition.operate(10, 5));
}
}@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
MathOperation addition = (a, b) -> a + b;
System.out.println(addition.operate(10, 5));
}
}@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
MathOperation addition = (a, b) -> a + b;
System.out.println(addition.operate(10, 5));
}
}@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
MathOperation addition = (a, b) -> a + b;
System.out.println(addition.operate(10, 5));
}
}Références de Méthodes – Un Raccourci pour les Lambdas
Au lieu d'écrire une expression lambda complète, utilisez une référence de méthode.
import java.util.function.Consumer;
public class MethodReferenceExample {
public static void main(String[] args) {
Consumer<String> print = System.out::println;
print.accept("Hello, method references!");
}
}import java.util.function.Consumer;
public class MethodReferenceExample {
public static void main(String[] args) {
Consumer<String> print = System.out::println;
print.accept("Hello, method references!");
}
}import java.util.function.Consumer;
public class MethodReferenceExample {
public static void main(String[] args) {
Consumer<String> print = System.out::println;
print.accept("Hello, method references!");
}
}import java.util.function.Consumer;
public class MethodReferenceExample {
public static void main(String[] args) {
Consumer<String> print = System.out::println;
print.accept("Hello, method references!");
}
}Coder comme un pro du fonctionnel : Adopter la programmation fonctionnelle en Java
Très bien, maintenant que vous êtes à l’aise avec les interfaces fonctionnelles, les lambdas et les références de méthode, passons au niveau supérieur. 🚀
L'approche fonctionnelle vs. l'approche traditionnelle
Dans le monde classique de la programmation orientée objet, nous écrivions une méthode pour filtrer les nombres pairs comme ceci :
import java.util.ArrayList;
import java.util.List;
class NumberFilter {
static List<Integer> filterEvenNumbers(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
return result;
}
}import java.util.ArrayList;
import java.util.List;
class NumberFilter {
static List<Integer> filterEvenNumbers(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
return result;
}
}import java.util.ArrayList;
import java.util.List;
class NumberFilter {
static List<Integer> filterEvenNumbers(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
return result;
}
}import java.util.ArrayList;
import java.util.List;
class NumberFilter {
static List<Integer> filterEvenNumbers(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
return result;
}
}
Cela fonctionne, mais c'est verbeux, procédural et donne l'impression de microgérer l'ordinateur au lieu de le laisser faire son travail.
Passons maintenant à une approche fonctionnelle ! 🎸
@FunctionalInterface
interface Condition<T> {
boolean test(T t);
}@FunctionalInterface
interface Condition<T> {
boolean test(T t);
}@FunctionalInterface
interface Condition<T> {
boolean test(T t);
}@FunctionalInterface
interface Condition<T> {
boolean test(T t);
}Maintenant, au lieu de coder la logique en dur dans une méthode, rendons-la plus flexible :
import java.util.List;
class NumberFilter{
static List<Integer> filter(List<Integer> numbers, Condition<Integer> condition) {
return numbers.stream()
.filter(condition::test)
.toList();
}
}import java.util.List;
class NumberFilter{
static List<Integer> filter(List<Integer> numbers, Condition<Integer> condition) {
return numbers.stream()
.filter(condition::test)
.toList();
}
}import java.util.List;
class NumberFilter{
static List<Integer> filter(List<Integer> numbers, Condition<Integer> condition) {
return numbers.stream()
.filter(condition::test)
.toList();
}
}import java.util.List;
class NumberFilter{
static List<Integer> filter(List<Integer> numbers, Condition<Integer> condition) {
return numbers.stream()
.filter(condition::test)
.toList();
}
}
Cette méthode peut désormais filtrer n'importe quoi en fonction de la condition que nous lui passons. 🎉
L'utiliser comme un pro
Au lieu d'écrire toute une classe pour filtrer les nombres pairs, il suffit de passer une lambda :
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = NumberFilter.filter(numbers, n -> n % 2 == 0);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = NumberFilter.filter(numbers, n -> n % 2 == 0);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = NumberFilter.filter(numbers, n -> n % 2 == 0);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = NumberFilter.filter(numbers, n -> n % 2 == 0);
System.out.println("Even numbers: " + evens);
}
}🔥 Boom ! Vous venez d'écrire du code Java propre, réutilisable et fonctionnel, sans code inutile !
Aller encore plus loin : les références de méthode
Si vous avez déjà une méthode qui vérifie si un nombre est pair, vous pouvez vous passer totalement de la lambda :
import java.util.List;
class MathUtils {
static boolean isEven(Integer n) {
return n % 2 == 0;
}
}
class Main {
public static void main(String[] args) {
List<Integer> evens = NumberFilterFunctional.filter(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), MathUtils::isEven);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
class MathUtils {
static boolean isEven(Integer n) {
return n % 2 == 0;
}
}
class Main {
public static void main(String[] args) {
List<Integer> evens = NumberFilterFunctional.filter(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), MathUtils::isEven);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
class MathUtils {
static boolean isEven(Integer n) {
return n % 2 == 0;
}
}
class Main {
public static void main(String[] args) {
List<Integer> evens = NumberFilterFunctional.filter(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), MathUtils::isEven);
System.out.println("Even numbers: " + evens);
}
}import java.util.List;
class MathUtils {
static boolean isEven(Integer n) {
return n % 2 == 0;
}
}
class Main {
public static void main(String[] args) {
List<Integer> evens = NumberFilterFunctional.filter(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), MathUtils::isEven);
System.out.println("Even numbers: " + evens);
}
}En résumé
Les interfaces fonctionnelles en Java 8 rendent votre code plus propre, plus lisible et moins verbeux. Que vous filtriez des listes, transformiez des données ou réduisiez simplement le code répétitif, elles apportent une nouvelle façon d’écrire du Java. Et franchement, qui n’aime pas taper moins de code ?
Maintenant que vous connaissez l’essentiel (mais le plus important) sur les interfaces fonctionnelles, lancez-vous et adoptez le côté fonctionnel de Java !