CodeGym /Cours /JAVA 25 SELF /Implémentation multiple d'interfaces

Implémentation multiple d'interfaces

JAVA 25 SELF
Niveau 20 , Leçon 2
Disponible

1. Introduction

En Java, il est impossible d'hériter de plusieurs classes à la fois. Cela a été fait délibérément pour éviter le « problème du diamant », une situation où deux classes parentes définissent la même méthode et où il est difficile de savoir quelle implémentation utiliser. En revanche, on peut implémenter autant d'interfaces que l'on veut. Pourquoi ? Parce qu'une interface est un simple contrat, elle ne contient pas d'implémentation (jusqu'à Java 8 — sans implémentation ; à partir de Java 8 — il peut y avoir des méthodes default et static). Par conséquent, aucune confusion liée à l'héritage de code ne se produit.

C'est comparable à une situation réelle : vous pouvez être à la fois « Conducteur », « Utilisateur d'ordinateur » et « Nageur ». Chacune de ces « interfaces » décrit des compétences particulières, sans vous obliger à être une copie d'une autre personne.

Syntaxe de l'implémentation multiple d'interfaces

En Java, une classe peut implémenter plusieurs interfaces en les énumérant, séparées par des virgules, après le mot-clé implements. Voici un exemple de base :

public interface Movable {
    void move(int x, int y);
}

public interface Chargeable {
    void charge();
}

public class Robot implements Movable, Chargeable {
    @Override
    public void move(int x, int y) {
        System.out.println("Le robot se deplace vers le point (" + x + ", " + y + ")");
    }

    @Override
    public void charge() {
      System.out.println("Le robot se recharge.");
    }
}

Dans cet exemple, Robot est polyvalent : il se déplace et se recharge. Comme dans la vie : plus vous savez faire, plus on vous invite en entretien !

2. Pourquoi en a-t-on besoin ? Exemples pratiques

Exemple 1. Différents « rôles » d'un objet

Imaginez que vous concevez un personnage de jeu :

  • Il peut se déplacer (Movable)
  • Il peut attaquer (Attackable)
  • Il peut etre sérialisé dans un fichier (Serializable — une telle interface existe dans la bibliothèque standard Java)
public interface Attackable {
    void attack();
}

public class Hero implements Movable, Attackable, java.io.Serializable {
    @Override
    public void move(int x, int y) {
        System.out.println("Le heros se deplace vers une nouvelle position.");
    }

    @Override
    public void attack() {
        System.out.println("Le heros porte un coup !");
    }
}

Votre classe peut maintenant etre utilisée dans des contextes très divers : vous pouvez la passer à des méthodes qui exigent n'importe laquelle de ces interfaces.

Exemple 2. Combiner des interfaces standard

Très souvent, la bibliothèque standard Java propose les interfaces Comparable (pour comparer des objets) et Serializable (pour enregistrer des objets dans un fichier ou les transmettre sur le réseau). Il arrive qu'un objet doive etre les deux :

public class Person implements Comparable<Person>, java.io.Serializable {
    private String name;
    private int age;

    public Person(String name, int age) { this.name = name; this.age = age; }

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }
}

Les objets Person peuvent désormais etre triés (par exemple, dans une liste) et écrits dans un fichier.

3. Particularités et limitations

Une seule implémentation par méthode

Si deux interfaces définissent une méthode avec la meme signature, vous n'avez à l'implémenter qu'une seule fois. Exemple :

public interface A {
    void doSomething();
}
public interface B {
    void doSomething();
}
public class MyClass implements A, B {
    @Override
    public void doSomething() {
        System.out.println("Implementation de doSomething pour les deux interfaces.");
    }
}

Java ne se plaindra pas — l'essentiel est que les signatures coïncident. Si les méthodes diffèrent par leur signature, elles sont considérées comme distinctes — chacune doit etre implémentée.

Pas de « problème du diamant »

Contrairement à l'héritage multiple de classes, implémenter plusieurs interfaces n'entraine pas l'héritage de deux implémentations différentes de la meme méthode. Jusqu'à Java 8, les interfaces n'avaient pas d'implémentation ; avec l'arrivée des méthodes default, en cas de conflit vous devez le résoudre explicitement (nous en parlerons plus en détail dans la leçon suivante).

Pas d'état

Les interfaces ne peuvent pas contenir de champs ordinaires (seulement des constantes — public static final). Il n'y a donc pas de confusion avec « deux champs parents portant le meme nom ».

4. Exemple : implémenter plusieurs interfaces dans une meme classe

Ajoutons à notre application pédagogique (par exemple, un zoo) de nouvelles possibilités. Supposons que nous ayons des animaux qui peuvent se déplacer et émettre un son :

public interface Movable {
    void move(int x, int y);
}

public interface Soundable {
    void makeSound();
}

public class Dog implements Movable, Soundable {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public void move(int x, int y) {
        System.out.println(name + " court vers (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " dit : Wouf wouf !");
    }
}

public class Cat implements Movable, Soundable {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void move(int x, int y) {
        System.out.println(name + " se faufile vers (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " dit : Miaou !");
    }
}

Nous pouvons maintenant écrire une méthode générique pour travailler avec n'importe quel objet « mobile » ou « sonore » :

public static void testMovable(Movable m) {
    m.move(10, 20);
}

public static void testSoundable(Soundable s) {
    s.makeSound();
}

public static void main(String[] args) {
    Dog rex = new Dog("Reks");
    Cat murka = new Cat("Murka");

    testMovable(rex);       // Reks court vers (10, 20)
    testSoundable(murka);   // Murka dit : Miaou !
}

Et bien sur, si un objet implémente les deux interfaces, vous pouvez le passer aux deux !

6. Nuances utiles

Et si les interfaces sont en conflit ?

Il arrive que deux interfaces définissent des méthodes avec la meme signature mais un sens différent. Par exemple, une interface s'attend à ce que la méthode reset() réinitialise des coordonnées, tandis qu'une autre souhaite que cette meme méthode éteigne un appareil. Dans ce cas, il faut etre vigilant : la méthode doit tout de meme etre implémentée une seule fois, et elle doit couvrir les deux comportements (ou au moins décider quoi faire). Dans la pratique, ces situations sont rares, mais si elles surviennent, il convient de reconsidérer la conception.

Exemple avec une collection d'objets de différentes interfaces

Supposons que nous ayons une liste d'objets implémentant différentes interfaces. Nous pouvons les parcourir et appeler les méthodes requises :

Movable[] movables = {
    new Dog("Sharik"),
    new Cat("Barsik"),
    new Robot()
};

for (Movable m : movables) {
    m.move(0, 0);
}

De la meme manière, on peut le faire pour n'importe quelle interface.

7. Erreurs courantes lors de l'implémentation multiple d'interfaces

Erreur n° 1 : toutes les méthodes des interfaces ne sont pas implémentées.
Si une classe déclare qu'elle implémente une interface mais n'implémente ne serait-ce qu'une seule de ses méthodes, le compilateur signale immédiatement une erreur. N'oubliez aucune méthode, meme si certaines vous paraissent « superflues ».

Erreur n° 2 : méthodes en conflit avec la meme signature.
Si deux interfaces définissent des méthodes identiques, vous ne devez les implémenter qu'une seule fois. Mais si le sens de ces méthodes diffère, cela peut mener à de la confusion et à des bugs. Dans ce cas, mieux vaut repenser l'architecture.

Erreur n° 3 : tentative d'hériter d'une interface avec extends dans une classe.
Dans une classe, on utilise toujours implements pour implémenter une interface, et non extends. Par exemple :

public class MyClass implements A, B { ... } // correct
public class MyClass extends A, B { ... }    // erreur !

Erreur n° 4 : tentative de créer une instance d'interface.
Une interface est un contrat, on ne peut pas l'instancier directement :

Movable m = new Movable(); // erreur de compilation

On ne peut créer que des objets de classes qui implémentent l'interface.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION