1. Introduction
Il fut un temps (avant Java 8) où une interface était très stricte : on ne pouvait y déclarer que des méthodes abstraites (sans implémentation) et des constantes (public static final). C’était pratique jusqu’à l’apparition d’un grand problème : l’évolution des bibliothèques.
Imaginez la situation
Vous avez développé une bibliothèque populaire dans laquelle il y a une interface :
public interface Movable {
void move(int x, int y);
}
Des milliers de développeurs dans le monde entier écrivent leurs classes implémentant cette interface. Quelques années plus tard, vous vous rendez compte qu’il manque à tout le monde une méthode reset(), qui remet l’objet à sa position initiale. Vous l’ajoutez à l’interface :
public interface Movable {
void move(int x, int y);
void reset();
}
Et c’est l’apocalypse : tous les projets qui utilisent votre interface cessent de compiler ! En effet, ils sont désormais tenus d’implémenter la nouvelle méthode, dont personne n’avait connaissance. La migration devient pénible.
Les méthodes default — la solution !
Java 8 a introduit les méthodes default : on peut désormais ajouter une méthode avec implémentation directement dans une interface ! Toutes les anciennes classes reçoivent automatiquement une implémentation par défaut, et leur code ne se casse pas. Et si vous le souhaitez, vous pouvez redéfinir la méthode à votre façon.
2. Syntaxe des méthodes default
Une méthode default est une méthode ordinaire avec implémentation au sein d’une interface, marquée par le mot-clé default.
public interface Movable {
void move(int x, int y);
default void reset() {
// Implémentation typique : retour à l’origine (0,0)
move(0, 0);
}
}
Explication :
- Toutes les méthodes d’une interface sont par défaut public et abstract, mais les méthodes default ne sont pas abstraites : elles ont un corps.
- Le mot‑clé default est toujours écrit avant le type de retour de la méthode.
À quoi cela ressemble-t-il dans une classe ?
public class Robot implements Movable {
private int x, y;
@Override
public void move(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Le robot a été déplacé vers (" + x + ", " + y + ")");
}
// Pas besoin d’implémenter reset() — la version par défaut fonctionnera !
}
Désormais, si nous appelons reset() sur un objet Robot, l’implémentation de l’interface Movable sera utilisée :
public class Main {
public static void main(String[] args) {
Movable robot = new Robot();
robot.move(10, 20); // Le robot a été déplacé vers (10, 20)
robot.reset(); // Le robot a été déplacé vers (0, 0)
}
}
3. Méthodes default dans la bibliothèque standard
Les méthodes default n’ont pas été ajoutées par hasard, mais pour permettre de faire évoluer les grandes interfaces standard de Java sans casser l’ancien code.
Exemple : interface List (Java 8+)
Dans Java 8, des méthodes avec implémentation ont été ajoutées à l’interface List, par exemple forEach, replaceAll, sort :
default void forEach(Consumer<Entity> action) {
for (Entity e : this) {
action.accept(e);
}
}
Si vous implémentez votre propre liste et que vous ne redéfinissez pas forEach, elle fonctionnera quand même — grâce à la méthode default.
Pour plus de détails sur les types génériques (Consumer<Entity>), vous l’apprendrez au niveau 26 :P
4. Pourquoi a-t-on besoin des méthodes default ?
- Évolution de l’API sans casser le code : on peut ajouter de nouvelles méthodes dans une interface sans devoir les implémenter dans toutes les classes existantes.
- Modèles de comportement universels : on peut définir un comportement par défaut que les classes peuvent utiliser ou redéfinir.
- Réduction de la duplication : si le comportement est identique pour la plupart des implémentations, il n’est pas nécessaire de copier le code dans chaque classe.
Analogie
Imaginez que vous avez un contrat de location d’appartement (interface). Il y était autrefois écrit : « Le locataire doit payer l’eau ». Puis on a ajouté : « Le locataire doit payer l’électricité ». Sans les méthodes default, il aurait fallu réécrire tous les contrats avec tous les locataires ! Avec les méthodes default, on ajoute simplement une clause, et si nécessaire — les parties peuvent convenir d’un autre arrangement.
5. Limitations et particularités des méthodes default
Les méthodes default ne peuvent pas redéfinir les méthodes de la classe Object
Vous ne pouvez pas déclarer dans une interface une méthode default dont la signature coïncide avec equals, hashCode ou toString de la classe Object. C’est une protection contre la confusion : tout objet en Java possède déjà ces méthodes.
// Erreur de compilation !
interface Broken {
default boolean equals(Object obj) { return false; }
}
Conflits entre méthodes default
Que se passe-t-il si une classe implémente deux interfaces, chacune ayant une méthode default avec la même signature ? Le compilateur Java dira honnêtement : « Décide toi-même, je ne sais pas quoi faire ! »
interface A {
default void hello() { System.out.println("Hello from A"); }
}
interface B {
default void hello() { System.out.println("Hello from B"); }
}
class C implements A, B {
// Il faut absolument résoudre le conflit :
@Override
public void hello() {
// Vous pouvez choisir quelle méthode appeler, ou en fournir une propre
A.super.hello(); // ou B.super.hello();
}
}
Si vous ne redéfinissez pas hello() dans la classe C, vous obtiendrez une erreur de compilation.
Les méthodes default peuvent appeler d’autres méthodes de l’interface
Une méthode default peut appeler d’autres méthodes de l’interface, même abstraites. L’essentiel est que l’implémentation soit fournie dans la classe.
interface Printer {
void print(String text);
default void printTwice(String text) {
print(text);
print(text);
}
}
6. Exemple : faire évoluer une application avec une méthode default
Voyons un exemple d’utilisation des méthodes default dans l’interface Movable :
public interface Movable {
void move(int x, int y);
default void reset() {
move(0, 0);
}
}
Et il y a une classe Robot qui implémente cette interface :
public class Robot implements Movable {
private int x = 5;
private int y = 7;
@Override
public void move(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Le robot a été déplacé vers (" + x + ", " + y + ")");
}
// Nous n’implémentons pas reset() — nous utilisons la méthode par défaut !
}
Essayons maintenant d’appeler les deux méthodes :
public class Main {
public static void main(String[] args) {
Movable robot = new Robot();
robot.move(10, 20); // Le robot a été déplacé vers (10, 20)
robot.reset(); // Le robot a été déplacé vers (0, 0)
}
}
Si nous voulons que Robot se réinitialise d’une manière particulière — il suffit de redéfinir reset() dans la classe :
@Override
public void reset() {
System.out.println("Le robot s’éteint et retourne à la base !");
move(0, 0);
}
7. Méthodes default et implémentation multiple des interfaces
Les méthodes default sont particulièrement utiles lorsqu’une classe implémente plusieurs interfaces. Mais il existe une nuance : si les deux interfaces possèdent une méthode default avec la même signature, le compilateur exigera une résolution explicite du conflit.
Exemple de conflit
interface A {
default void show() { System.out.println("A"); }
}
interface B {
default void show() { System.out.println("B"); }
}
class C implements A, B {
@Override
public void show() {
// Nous choisissons explicitement quelle méthode default utiliser
A.super.show(); // ou B.super.show();
}
}
8. Schéma: fonctionnement d’un appel de méthode default
+-------------------+
| Movable |
|-------------------|
| +move(int, int) | <- méthode abstraite
| +reset() | <- méthode default
+-------------------+
^
|
+-------------------+
| Robot |
|-------------------|
| +move(int, int) | <- implémente
| | (reset() n’est pas implémentée)
+-------------------+
|
Appel de reset()
|
Implémentation de l’interface Movable utilisée
9. Erreurs courantes lors de l’utilisation des méthodes default
Erreur n°1 : tentative de créer une méthode default sans implémentation.
Une méthode default doit obligatoirement avoir un corps ! Si vous écrivez default void foo();, le compilateur dira aussitôt : « Tu as oublié les accolades ? »
Erreur n°2 : conflit de méthodes default provenant d’interfaces différentes.
Si une classe implémente deux interfaces avec la même méthode default, vous devez résoudre le conflit explicitement — sinon le compilateur refusera de compiler le code.
Erreur n°3 : tentative de déclarer une méthode default avec la signature d’une méthode de Object.
On ne peut pas créer une méthode default equals, hashCode ou toString dans une interface — uniquement des méthodes abstraites portant ces noms.
Erreur n°4 : oublier que les méthodes default ne sont pas de la « magie », mais simplement un outil pratique.
Les méthodes default n’annulent pas le principe selon lequel une interface est un contrat. Si le comportement par défaut ne convient pas, redéfinissez toujours la méthode default dans la classe.
GO TO FULL VERSION