1. Introduction
Interface fonctionnelle — c’est une interface qui contient exactement une seule méthode abstraite (c’est‑à‑dire une méthode sans implémentation). C’est précisément ce type d’interface qui peut être utilisé pour écrire une implémentation de méthode de façon concise — en Java, on le fait à l’aide d’expressions lambda (nous les étudierons plus tard).
Pourquoi une seule méthode ?
Parce qu’une interface fonctionnelle décrit exactement une opération. S’il y avait deux méthodes ou plus, il deviendrait difficile de savoir quelle méthode implémenter. La règle est donc simple : une interface — une seule méthode abstraite.
Exemples de la bibliothèque standard
- Runnable — pour des tâches dans des threads (void run())
- Callable<V> — pour des tâches qui renvoient un résultat (V call())
- Comparator<T> — pour comparer des objets (int compare(T o1, T o2))
- Consumer<T> — « consommateur » de valeur (void accept(T t))
- Supplier<T> — « fournisseur » de valeur (T get())
- Function<T, R> — fonction de T vers R (R apply(T t))
- Predicate<T> — vérification d’une condition (boolean test(T t))
Voici, par exemple, l’interface Runnable :
public interface Runnable {
void run();
}
Et voici l’interface Comparator :
public interface Comparator<T> {
int compare(T o1, T o2);
// ... il y a aussi des méthodes default et static, mais il n'y a qu'une seule méthode abstraite !
}
Point important : les default- et static-méthodes ne sont pas considérées comme abstraites ; il peut donc y en avoir autant que vous le souhaitez !
2. L’annotation @FunctionalInterface
Java est stricte et exigeante. Pour éviter toute confusion, elle permet de marquer explicitement une interface comme fonctionnelle à l’aide de l’annotation @FunctionalInterface. C’est comme un autocollant « Fonctionne avec un seul bouton ! » — pour que personne n’ajoute quoi que ce soit de superflu.
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Désormais, si vous oubliez et ajoutez une deuxième méthode abstraite, le compilateur signalera immédiatement une erreur :
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // Erreur ! Deux méthodes abstraites
}
L’annotation est-elle obligatoire ?
Non, ce n’est pas obligatoire. L’interface sera fonctionnelle même sans elle si elle contient exactement une méthode abstraite. Mais avec l’annotation, vous affichez clairement votre intention et vous vous protégez des erreurs accidentelles.
Peut-on ajouter des méthodes default et static ?
Oui ! L’essentiel est qu’il n’y ait qu’une seule méthode abstraite. Toutes les autres méthodes peuvent être default ou static, autant que vous voulez.
Exemple :
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("Je suis une opération fancy !");
}
static void description() {
System.out.println("Interface fonctionnelle pour l'arithmétique.");
}
}
3. Exemples de déclaration et d’utilisation
Supposons que vous vouliez décrire une opération sur deux nombres. Voici comment faire :
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
On peut ensuite implémenter cette interface de différentes manières.
Implémentation via une classe ordinaire
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
Utilisation :
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
Implémentation via une classe anonyme
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
Remarque au sujet des lambdas
Depuis Java 8, il est pratique d’implémenter de telles interfaces via des expressions lambda — une syntaxe plus concise. Nous étudierons les lambdas dans quelques cours, donc pour l’instant il suffit de savoir que les interfaces fonctionnelles ont été conçues pour être utilisées le plus facilement possible.
4. Pratique : écrivez votre propre interface fonctionnelle
Exercice 1. Créez votre propre Action !
Créez l’interface Action, qui prend une chaîne et ne renvoie rien. Implémentez-la via une classe anonyme qui imprime la chaîne en majuscules.
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("J'apprends Java !"); // J'APPRENDS JAVA !
}
}
(Plus tard, nous verrons comment écrire cela de manière plus concise à l’aide des expressions lambda.)
Exercice 2. Filtre de nombres
Créez l’interface NumberPredicate avec la méthode boolean test(int n). Implémentez la vérification de la parité à l’aide d’une classe anonyme.
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
Exercice 3. Utiliser les interfaces standard
Au lieu d’une interface maison, on peut utiliser la prête à l’emploi Predicate<Integer> :
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
Tableau : interfaces fonctionnelles de la bibliothèque standard
| Interface | Méthode | Description | Exemple d’utilisation |
|---|---|---|---|
|
|
Tâche sans arguments ni résultat | Threads, timers |
|
|
Tâche avec résultat | Threads, ExecutorService |
|
|
Comparaison de deux objets | Tri des collections |
|
|
« Consommateur » de valeur | Itération sur une collection |
|
|
« Fournisseur » de valeur | Initialisation paresseuse, génération de données |
|
|
Fonction de T vers R | Transformation de données |
|
|
Vérification d’une condition | Filtrage de collections |
5. Erreurs courantes avec les interfaces fonctionnelles
Erreur n° 1 : vous avez ajouté une deuxième méthode abstraite. S’il y a plus d’une méthode abstraite dans l’interface, elle cesse d’être fonctionnelle. Le compilateur (surtout avec @FunctionalInterface) le signalera immédiatement.
Erreur n° 2 : vous avez oublié que les méthodes default et static ne sont pas considérées comme abstraites. On peut les ajouter sans crainte dans une interface fonctionnelle — cela ne viole pas la règle « une seule méthode abstraite ».
Erreur n° 3 : signature de méthode incorrectement implémentée. Par exemple, l’interface exige deux arguments, mais vous avez écrit une méthode avec un seul. Vérifiez toujours les signatures.
Erreur n° 4 : vous n’utilisez pas @FunctionalInterface et vous cassez l’interface par inadvertance. Si vous ne marquez pas l’interface avec l’annotation, vous pouvez ajouter accidentellement une méthode de trop — et passer du temps à chercher pourquoi le code ne fonctionne pas. Mieux vaut toujours ajouter l’annotation pour plus de clarté.
GO TO FULL VERSION