Comment implémenter les interfaces en Java?

Dans le Chapitre 6, vous avez appris sur les classes abstraites, particulièrement comment en créer et en étendre une. Puisque les classes ne peuvent étendre qu’une seule classe, elles ont une utilisation limitée pour l’héritage. D’autre part, une classe peut implémenter n’importe quel nombre d’interfaces. Une interface est un type de données abstrait qui déclare une liste de méthodes abstraites que toute classe implémentant l’interface doit fournir.

Au fil du temps, la définition précise d’une interface a changé, car de nouveaux types de méthodes sont maintenant pris en charge. Dans ce chapitre, nous commençons par une définition rudimentaire d’une interface et l’étendons pour couvrir tous les membres pris en charge.

Déclaration et Utilisation d’une Interface

En Java, une interface est définie avec le mot-clé interface, analogue au mot-clé class utilisé lors de la définition d’une classe. Référez-vous à la Figure 7.1 pour une déclaration d’interface appropriée.

L’illustration originale montre la syntaxe détaillée d’une déclaration d’interface avec l’exemple suivant:

public abstract interface PeutCreuser {
    public abstract Float getVitesse(int age);
    public static final int PROFONDEUR_MINIMUM = 2;
}

Cette figure illustre les modificateurs implicites et la structure d’une interface.

Dans une déclaration d’interface, nous incluons généralement des méthodes abstraites et des variables constantes. Les variables d’interface sont appelées constantes car elles sont supposées être public, static, et final. Elles sont initialisées avec une valeur constante lorsqu’elles sont déclarées. Puisqu’elles sont public et static, elles peuvent être utilisées en dehors de la déclaration d’interface sans nécessiter une instance de l’interface. Une interface inclut également des méthodes abstraites qui, comme les variables d’interface, sont supposées être public.

Pour être bref, nous disons souvent “une instance d’une interface” dans ce chapitre pour signifier une instance d’une classe qui implémente l’interface.

Que signifie pour une variable ou une méthode d’être supposée être quelque chose? Un aspect d’une déclaration d’interface qui diffère d’une classe abstraite est qu’elle contient des modificateurs implicites. Un modificateur implicite est un modificateur que le compilateur insère automatiquement dans le code. Par exemple, une interface est toujours considérée comme étant abstract, même si elle n’est pas marquée comme telle. Nous couvrons les règles et exemples pour les modificateurs implicites plus en détail sous peu.

Commençons par un exemple simple. Imaginons que nous avons une interface MarcheSurDeuxJambes, définie comme suit:

public abstract interface MarcheSurDeuxJambes {}

Elle compile car les interfaces ne sont pas obligées de définir des méthodes. Le modificateur abstract dans cet exemple est optionnel pour les interfaces, le compilateur l’insérant s’il n’est pas fourni. Maintenant, considérez les deux exemples suivants, qui ne compilent pas:

public class Bipede {
    public static void main(String[] args) {
        var e = new MarcheSurDeuxJambes(); // NE COMPILE PAS
    }
}

public final interface MarcheSurHuitPattes {} // NE COMPILE PAS

Le premier exemple ne compile pas, car MarcheSurDeuxJambes est une interface et ne peut pas être instanciée. Le deuxième exemple, MarcheSurHuitPattes, ne compile pas car les interfaces ne peuvent pas être marquées comme final pour la même raison que les classes abstraites ne peuvent pas être marquées comme final. En d’autres termes, marquer une interface final implique qu’aucune classe ne pourrait jamais l’implémenter.

Comment utilisez-vous une interface? Disons que nous avons une interface Grimper, définie comme suit:

public interface Grimper {
    Number getVitesse(int age);
}

Ensuite, nous avons une classe concrète SourisDeLaChamp qui invoque l’interface Grimper en utilisant le mot-clé implements dans sa déclaration de classe.

L’illustration originale montre une classe implémentant une interface avec l’exemple suivant:

public class SourisDeLaChamp implements Grimper, PeutCreuser {
    public Float getVitesse(int age) {
        return 11f;
    }
}

Cette figure illustre la syntaxe d’implémentation d’interfaces multiples et la surcharge de méthodes d’interface.

La classe SourisDeLaChamp déclare qu’elle implémente l’interface Grimper et inclut une version surchargée de getVitesse() héritée de l’interface Grimper. La signature de la méthode getVitesse() correspond exactement, et le type de retour est covariant, puisqu’un Float peut être implicitement converti en un Number. Le modificateur d’accès de la méthode d’interface est implicitement public dans Grimper, bien que la classe concrète SourisDeLaChamp doive le déclarer explicitement.

Comme le montre la Figure 7.2, une classe peut implémenter plusieurs interfaces, chacune séparée par une virgule (,). Si l’une des interfaces définit des méthodes abstraites, alors la classe concrète est requise pour les surcharger. Dans ce cas, SourisDeLaChamp implémente l’interface PeutCreuser que nous avons vue dans la Figure 7.1. De cette manière, la classe surcharge deux méthodes abstraites en même temps avec une déclaration de méthode. Vous en apprendrez davantage sur les méthodes d’interface dupliquées et compatibles dans ce chapitre.

Extension d’une Interface

Comme une classe, une interface peut étendre une autre interface en utilisant le mot-clé extends.

public interface Nocturne {}
public interface AGrosYeux extends Nocturne {}

Contrairement à une classe, qui ne peut étendre qu’une seule classe, une interface peut étendre plusieurs interfaces.

public interface Nocturne {
    public int chasser();
}

public interface PeutVoler {
    public void battreDesAiles();
}

public interface AGrosYeux extends Nocturne, PeutVoler {}

public class Hibou implements AGrosYeux {
    public int chasser() { return 5; }
    public void battreDesAiles() { System.out.println("Batt!"); }
}

Dans cet exemple, la classe Hibou implémente l’interface AGrosYeux et doit implémenter les méthodes chasser() et battreDesAiles(). L’extension de deux interfaces est permise car les interfaces ne sont pas initialisées dans le cadre d’une hiérarchie de classes. Contrairement aux classes abstraites, elles ne contiennent pas de constructeurs et ne font pas partie de l’initialisation d’instance. Les interfaces définissent simplement un ensemble de règles et de méthodes qu’une classe les implémentant doit suivre.

Héritage d’une Interface

Comme une classe abstraite, lorsqu’une classe concrète hérite d’une interface, toutes les méthodes abstraites héritées doivent être implémentées. Nous illustrons ce principe ci-dessous. Combien de méthodes abstraites la classe concrète Cygne hérite-t-elle?

Note sur la Figure 7.3 (Héritage d’Interface): L’illustration originale montre un diagramme d’héritage avec:

  • Une classe abstraite Animal avec une méthode abstraite getType()
  • Une classe abstraite Oiseau qui étend Animal et ajoute une méthode abstraite peutPlonger()
  • Une interface Voler avec une méthode voler()
  • Une interface Nager avec une méthode nager()
  • La classe Cygne qui étend Oiseau et implémente les interfaces Voler et Nager

Vous abandonnez? La classe concrète Cygne hérite de quatre méthodes abstraites qu’elle doit implémenter: getType(), peutPlonger(), voler(), et nager(). Voyons un autre exemple impliquant une classe abstraite qui implémente une interface:

public interface AUneQueue {
    public int getLongueurQueue();
}

public interface AMoustaches {
    public int getNombreMoustaches();
}

public abstract class PhoqueDePort implements AUneQueue, AMoustaches {}

public class PhoqueCommun extends PhoqueDePort {} // NE COMPILE PAS

La classe PhoqueDePort compile car elle est abstract et n’est pas obligée d’implémenter l’une des méthodes abstraites qu’elle hérite. La classe concrète PhoqueCommun, cependant, doit surcharger toutes les méthodes abstraites héritées.

Mélange des Mots-clés de Classe et d’Interface

Soyez attentif aux exemples qui mélangent les déclarations de classe et d’interface.

Bien qu’une classe puisse implémenter une interface, une classe ne peut pas étendre une interface. De même, bien qu’une interface puisse étendre une autre interface, une interface ne peut pas implémenter une autre interface. Les exemples suivants illustrent ces principes:

public interface PeutCourir {}
public class Guepard extends PeutCourir {} // NE COMPILE PAS

public class Hyene {}
public interface AFourrure extends Hyene {} // NE COMPILE PAS

Le premier exemple montre une classe essayant d’étendre une interface et ne compile pas. Le deuxième exemple montre une interface essayant d’étendre une classe, ce qui ne compile pas non plus.

Héritage de Méthodes Abstraites Dupliquées

Java prend en charge l’héritage de deux méthodes abstraites qui ont des déclarations de méthode compatibles.

public interface Herbivore { public void mangerPlantes(); }

public interface Omnivore { public void mangerPlantes(); }

public class Ours implements Herbivore, Omnivore {
    public void mangerPlantes() {
        System.out.println("Manger des plantes");
    }
}

Par compatibles, nous entendons qu’une méthode peut être écrite qui surcharge correctement les deux méthodes héritées: par exemple, en utilisant des types de retour covariants que vous avez appris dans le Chapitre 6.

Voici un exemple d’une déclaration incompatible:

public interface Herbivore { public void mangerPlantes(); }

public interface Omnivore { public int mangerPlantes(); }

public class Tigre implements Herbivore, Omnivore { // NE COMPILE PAS
    …
}

Il est impossible d’écrire une version de Tigre qui satisfait les deux méthodes abstraites héritées. Le code ne compile pas, quel que soit ce qui est déclaré à l’intérieur de la classe Tigre.

Insertion de Modificateurs Implicites

Comme mentionné précédemment, un modificateur implicite est celui que le compilateur insérera automatiquement. C’est semblable au compilateur insérant un constructeur sans argument par défaut si vous n’en définissez pas, ce que vous avez appris dans le Chapitre 6. Vous pouvez choisir d’insérer ces modificateurs implicites vous-même ou laisser le compilateur les insérer pour vous.

La liste suivante comprend les modificateurs implicites pour les interfaces que vous devez connaître:

  • Les interfaces sont implicitement abstract.
  • Les variables d’interface sont implicitement public, static, et final.
  • Les méthodes d’interface sans corps sont implicitement abstract.
  • Les méthodes d’interface sans le modificateur private sont implicitement public.

La dernière règle s’applique aux méthodes abstract, default, et static de l’interface, que nous couvrons dans la section suivante.

Jetons un coup d’œil à un exemple. Les deux définitions d’interface suivantes sont équivalentes, car le compilateur les convertira toutes les deux en la deuxième déclaration:

public interface Planer {
    int HAUTEUR_MAX = 10;
    boolean SOUS_EAU = true;
    void voler(int vitesse);
    abstract void decoller();
    public abstract double plonger();
}

public abstract interface Planer {
    public static final int HAUTEUR_MAX = 10;
    public final static boolean SOUS_EAU = true;
    public abstract void voler(int vitesse);
    public abstract void decoller();
    public abstract double plonger();
}

Dans cet exemple, nous avons marqué en gras les modificateurs implicites que le compilateur insère automatiquement. D’abord, le mot-clé abstract est ajouté à la déclaration de l’interface. Ensuite, les mots-clés public, static, et final sont ajoutés aux variables d’interface si elles n’existent pas déjà. Enfin, chaque méthode abstraite est précédée des mots-clés abstract et public si elle ne les contient pas déjà.

Modificateurs Conflictuels

Que se passe-t-il si un développeur marque une méthode ou une variable avec un modificateur qui entre en conflit avec un modificateur implicite? Par exemple, si une méthode abstraite est implicitement public, peut-elle être explicitement marquée protected ou private?

public interface Danse {
    private int compte = 4; // NE COMPILE PAS
    protected void pas(); // NE COMPILE PAS
}

Aucune de ces déclarations de membre d’interface ne compile, car le compilateur appliquera le modificateur public aux deux, résultant en un conflit.

Différences entre les Interfaces et les Classes Abstraites

Même si les classes abstraites et les interfaces sont toutes deux considérées comme des types abstraits, seules les interfaces utilisent des modificateurs implicites. En quoi les méthodes jouer() diffèrent-elles dans les deux définitions suivantes?

abstract class Husky { // abstract requis dans la déclaration de classe
    abstract void jouer(); // abstract requis dans la déclaration de méthode
}

interface Caniche { // abstract optionnel dans la déclaration d'interface
    void jouer(); // abstract optionnel dans la déclaration de méthode
}

Ces deux définitions de méthode sont considérées comme abstraites. Cela dit, la classe Husky ne compilera pas si la méthode jouer() n’est pas marquée abstract, alors que la méthode dans l’interface Caniche compilera avec ou sans le modificateur abstract.

Qu’en est-il du niveau d’accès de la méthode jouer()? Pouvez-vous repérer quelque chose d’incorrect avec les définitions de classe suivantes qui utilisent nos types abstraits?

public class Webby extends Husky {
    void jouer() {}      // OK - jouer() est déclarée avec accès package dans Husky
}

public class Georgette implements Caniche {
    void jouer() {}      // NE COMPILE PAS - jouer() est public dans Caniche
}

La classe Webby compile, mais la classe Georgette ne compile pas. Bien que les deux implémentations de méthode soient identiques, la méthode dans la classe Georgette réduit le modificateur d’accès sur la méthode de public à l’accès package.

Déclaration de Méthodes Concrètes d’Interface

Alors que les interfaces ont commencé avec des méthodes abstraites et des constantes, elles ont grandi pour inclure beaucoup plus. Le Tableau 7.1 liste les six types de membres d’interface que vous devez connaître. Nous avons déjà couvert les méthodes abstraites et les constantes, donc nous nous concentrons sur les quatre méthodes concrètes restantes dans cette section.

TABLEAU 7.1 Types de membres d’interface

Type de membreType d’adhésionModificateurs requisModificateurs implicitesA une valeur ou un corps?
Variable constanteClassepublic static finalOui
Méthode abstractInstancepublic abstractNon
Méthode defaultInstancedefaultpublicOui
Méthode staticClassestaticpublicOui
Méthode privateInstanceprivateOui
Méthode private staticClasseprivate staticOui

Dans le Tableau 7.1, le type d’adhésion détermine comment il est accessible. Une méthode avec un type d’adhésion de classe est partagée entre toutes les instances de l’interface, tandis qu’une méthode avec un type d’adhésion d’instance est associée à une instance particulière de l’interface.

À propos des Membres d’Interface protected ou Package

Aux côtés des méthodes public, les interfaces prennent maintenant en charge les méthodes private. Elles ne prennent pas en charge l’accès protected, cependant, car une classe ne peut pas étendre une interface. Elles ne prennent pas non plus en charge l’accès package, bien que plus probablement pour des raisons de syntaxe et de compatibilité descendante. Puisque les méthodes d’interface sans modificateur d’accès ont été considérées comme implicitement public, changer ce comportement en accès package briserait beaucoup de programmes existants!

Écriture d’une Méthode d’Interface default

Le premier type de méthode concrète avec lequel vous devriez vous familiariser est une méthode default. Une méthode default est une méthode définie dans une interface avec le mot-clé default et inclut un corps de méthode. Elle peut être optionnellement surchargée par une classe implémentant l’interface.

Une utilisation des méthodes default est pour la compatibilité descendante. Vous pouvez ajouter une nouvelle méthode default à une interface sans avoir besoin de modifier toutes les classes existantes qui implémentent l’interface. Les classes plus anciennes utiliseront simplement l’implémentation par défaut de la méthode définie dans l’interface. C’est d’où vient le nom de méthode default!

Voici un exemple d’une méthode default définie dans une interface:

public interface EstSangFroid {
    boolean aDesEcailles();
    default double getTemperature() {
        return 10.0;
    }
}

Cet exemple définit deux méthodes d’interface, une abstraite et une default. La classe Serpent suivante, qui implémente EstSangFroid, doit implémenter aDesEcailles() mais peut s’appuyer sur l’implémentation par défaut de getTemperature() ou surcharger la méthode avec sa propre version:

public class Serpent implements EstSangFroid {
    public boolean aDesEcailles() { // Surcharge requise
        return true;
    }
    
    public double getTemperature() { // Surcharge optionnelle
        return 12;
    }
}

Notez que le modificateur de méthode d’interface default n’est pas le même que l’étiquette default utilisée dans une instruction ou expression switch. De même, bien que l’accès package soit parfois appelé accès par défaut, cette fonctionnalité est implémentée en omettant un modificateur d’accès. Désolé si c’est déroutant! Nous sommes d’accord que Java a surutilisé le mot default au fil des ans!

Pour l’examen, vous devriez être familier avec diverses règles pour déclarer des méthodes default.

Règles de Définition des Méthodes d’Interface Default

  1. Une méthode default ne peut être déclarée que dans une interface.
  2. Une méthode default doit être marquée avec le mot-clé default et inclure un corps de méthode.
  3. Une méthode default est implicitement public.
  4. Une méthode default ne peut pas être marquée abstract, final, ou static.
  5. Une méthode default peut être surchargée par une classe qui implémente l’interface.
  6. Si une classe hérite de deux ou plusieurs méthodes default avec la même signature de méthode, alors la classe doit surcharger la méthode.

Ces règles devraient découler de ce que vous savez jusqu’à présent des classes, des interfaces et des méthodes static. Par exemple, vous ne pouvez pas déclarer des méthodes static sans corps dans les classes non plus. Comme les méthodes d’interface default et abstract, les méthodes d’interface static sont implicitement public si elles sont déclarées sans modificateur d’accès. Comme vous le verrez bientôt, vous pouvez utiliser le modificateur d’accès private avec les méthodes static.

Jetons un coup d’œil à une méthode d’interface static:

public interface Sauter {
    static int getHauteurSaut() {
        return 8;
    }
}

Puisque la méthode est définie sans modificateur d’accès, le compilateur insérera automatiquement le modificateur d’accès public. La méthode getHauteurSaut() fonctionne comme une méthode static telle que définie dans une classe. En d’autres termes, elle peut être accédée sans une instance d’une classe.

public class Sauter {
    public int sauter() {
        return Sauter.getHauteurSaut();
    }
}

La dernière règle sur l’héritage pourrait être un peu déroutante, alors regardons un exemple. Voici un exemple d’une classe Lapin qui implémente Sauter et ne compile pas:

public class Lapin implements Sauter {
    public void afficherDetails() {
        System.out.println(getHauteurSaut()); // NE COMPILE PAS
    }
}

Sans une référence explicite au nom de l’interface, le code ne compilera pas, même si Lapin implémente Sauter. Cela peut être facilement corrigé en utilisant le nom de l’interface:

public class Lapin implements Sauter {
    public void afficherDetails() {
        System.out.println(Sauter.getHauteurSaut());
    }
}

Remarquez que nous n’avons pas le même problème que lorsque nous avons hérité de deux méthodes d’interface default avec la même signature. Java a “résolu” le problème de l’héritage multiple des méthodes d’interface static en ne leur permettant pas d’être héritées!

Réutilisation de Code avec des Méthodes d’Interface private

Les deux derniers types de méthodes concrètes qui peuvent être ajoutées aux interfaces sont les méthodes d’interface private et private static. Comme les deux types de méthodes sont private, elles ne peuvent être utilisées que dans la déclaration d’interface dans laquelle elles sont déclarées. Pour cette raison, elles ont été ajoutées principalement pour réduire la duplication de code. Par exemple, considérez l’exemple de code suivant:

public interface Programme {
    default void reveil() { verifierHeure(7); }
    private void petitDejeuner() { verifierHeure(9); }
    static void faireExercice() { verifierHeure(18); }
    private static void verifierHeure(int heure) {
        if (heure > 17) {
            System.out.println("Vous êtes en retard!");
        } else {
            System.out.println("Vous avez encore "+(17-heure)+" heures "
                + "pour respecter le rendez-vous");
        }
    }
}

Vous pourriez écrire cette interface sans utiliser une méthode private en copiant le contenu de la méthode verifierHeure() aux endroits où elle est utilisée. C’est beaucoup plus court et plus facile à lire si vous ne le faites pas. Puisque les auteurs de Java ont été assez gentils pour ajouter cette fonctionnalité pour notre commodité, autant l’utiliser!

Nous aurions pu également déclarer verifierHeure() comme public dans l’exemple précédent, mais cela aurait exposé la méthode pour une utilisation en dehors de l’interface. Un principe important de l’encapsulation est de ne pas exposer le fonctionnement interne d’une classe ou d’une interface lorsque ce n’est pas nécessaire. Nous couvrons l’encapsulation plus tard dans ce chapitre.

La différence entre une méthode private non statique et une méthode static est analogue à la différence entre une méthode d’instance et une méthode static déclarée dans une classe. En particulier, tout concerne quelles méthodes chacune peut appeler.

Règles de Définition des Méthodes d’Interface Private

  1. Une méthode d’interface private doit être marquée avec le modificateur private et inclure un corps de méthode.
  2. Une méthode d’interface private static peut être appelée par toute méthode dans la définition de l’interface.
  3. Une méthode d’interface private ne peut être appelée que par des méthodes default et d’autres méthodes private non-static dans la définition de l’interface.

Une autre façon de penser est qu’une méthode d’interface private n’est accessible qu’aux méthodes non-static définies dans l’interface. Une méthode d’interface private static, d’autre part, peut être accédée par n’importe quelle méthode dans l’interface. Pour les deux types de méthodes private, une classe héritant de l’interface ne peut pas les invoquer directement.

Appel de Méthodes Abstract

Nous avons beaucoup parlé des nouveaux types de méthodes d’interface, mais qu’en est-il des méthodes abstract? Il s’avère que les méthodes default et private non-static peuvent accéder aux méthodes abstract déclarées dans l’interface. C’est la principale raison pour laquelle nous associons ces méthodes à l’adhésion d’instance. Lorsqu’elles sont invoquées, il y a une instance de l’interface.

public interface RenovationZoo {
    public String nomProjet();
    abstract String statut();
    default void afficherStatut() {
        System.out.print("Le projet " + nomProjet() + " " + statut());
    }
}

Dans cet exemple, nomProjet() et statut() ont les mêmes modificateurs (abstract et public sont implicites) et peuvent être appelés par la méthode default afficherStatut().

Revue des Membres d’Interface

Nous concluons notre discussion sur les membres d’interface avec le Tableau 7.2, qui montre les règles d’accès pour les membres dans et en dehors d’une interface.

TABLEAU 7.2 Accès aux membres d’interface

 Accessible depuis les méthodes default et private dans l’interface?Accessible depuis les méthodes static dans l’interface?Accessible depuis les méthodes dans les classes héritant de l’interface?Accessible sans une instance de l’interface?
Variable constanteOuiOuiOuiOui
Méthode abstractOuiNonOuiNon
Méthode defaultOuiNonOuiNon
Méthode staticOuiOuiOui (nom de l’interface requis)Oui (nom de l’interface requis)
Méthode privateOuiNonNonNon
Méthode private staticOuiOuiNonNon

Bien que le Tableau 7.2 puisse sembler beaucoup à mémoriser, voici quelques conseils rapides pour l’examen:

  • Traitez les méthodes abstract, default, et private non-static comme appartenant à une instance de l’interface.
  • Traitez les méthodes et variables static comme appartenant à l’objet de classe d’interface.
  • Tous les types de méthodes d’interface private ne sont accessibles que dans la déclaration d’interface.

En utilisant ces règles, laquelle des méthodes suivantes ne compile pas?

public interface VisiteTrainZoo {
    abstract int getNomTrain();
    private static void monter() {}
    default void jouerCorne() { getNomTrain(); monter(); }
    public static void ralentir() { jouerCorne(); }
    static void accelerer() { monter(); }
}

La méthode monter() est private et static, donc elle peut être accédée par n’importe quelle méthode default ou static dans la déclaration d’interface. La méthode getNomTrain() est abstract, donc elle peut être accédée par une méthode default associée à l’instance. La méthode ralentir() est static, cependant, et ne peut pas appeler une méthode default ou private, comme jouerCorne(), sans un objet de référence explicite. Par conséquent, la méthode ralentir() ne compile pas.

Donnez-vous une tape dans le dos! Vous venez d’apprendre beaucoup sur les interfaces, probablement plus que vous ne pensiez possible. Maintenant, prenez une profonde respiration. Prêt? Le prochain type que nous allons couvrir est les énumérations.

La première règle devrait vous réconforter car vous ne verrez des méthodes default que dans les interfaces. Si vous les voyez dans une classe ou une énumération à l’examen, quelque chose ne va pas. La deuxième règle dénote simplement la syntaxe, car les méthodes default doivent utiliser le mot-clé default. Par exemple, les extraits de code suivants ne compileront pas car ils mélangent les méthodes d’interface concrètes et abstraites:

public interface Carnivore {
    public default void mangerViande(); // NE COMPILE PAS
    public int getQuantiteNourritureRequise() { // NE COMPILE PAS
        return 13;
    }
}

Les trois règles suivantes pour les méthodes default découlent de la relation avec les méthodes d’interface abstraites. Comme les méthodes d’interface abstraites, les méthodes default sont implicitement public. Contrairement aux méthodes abstraites, cependant, les méthodes d’interface default ne peuvent pas être marquées abstract puisqu’elles fournissent un corps. Elles ne peuvent pas non plus être marquées comme final, car elles sont conçues pour qu’elles puissent être surchargées dans les classes implémentant l’interface, tout comme les méthodes abstraites. Enfin, elles ne peuvent pas être marquées static puisqu’elles sont associées à l’instance de la classe implémentant l’interface.

Héritage de Méthodes default Dupliquées

La dernière règle pour créer une méthode d’interface default nécessite quelques explications. Par exemple, quelle valeur le code suivant produirait-il?

public interface Marcher {
    public default int getVitesse() { return 5; }
}

public interface Courir {
    public default int getVitesse() { return 10; }
}

public class Chat implements Marcher, Courir {} // NE COMPILE PAS

Dans cet exemple, Chat hérite des deux méthodes default pour getVitesse(), alors laquelle utilise-t-il? Puisque Marcher et Courir sont considérés comme des frères et sœurs en termes de comment ils sont utilisés dans la classe Chat, il n’est pas clair si le code devrait produire 5 ou 10. Dans ce cas, le compilateur abandonne et dit: “Trop difficile, j’abandonne!” et échoue.

Tout n’est pas perdu, cependant. Si la classe implémentant les interfaces surcharge la méthode default dupliquée, le code compilera sans problème. En surchargeant la méthode conflictuelle, l’ambiguïté sur quelle version de la méthode appeler a été supprimée. Par exemple, l’implémentation modifiée suivante de Chat compilera:

public class Chat implements Marcher, Courir {
    public int getVitesse() { return 1; }
}

Appel d’une Méthode default Cachée

Dans la dernière section, nous avons montré comment notre classe Chat pouvait surcharger une paire de méthodes default conflictuelles, mais que se passe-t-il si la classe Chat voulait accéder à la version de getVitesse() dans Marcher ou Courir? Est-elle toujours accessible?

Oui, mais cela nécessite une syntaxe spéciale.

public class Chat implements Marcher, Courir {
    public int getVitesse() {
        return 1;
    }
    
    public int getVitesseMarche() {
        return Marcher.super.getVitesse();
    }
}

C’est un domaine où une méthode default présente des propriétés à la fois d’une méthode static et d’instance. Nous utilisons le nom de l’interface pour indiquer quelle méthode nous voulons appeler, mais nous utilisons le mot-clé super pour montrer que nous suivons l’héritage d’instance, pas l’héritage de classe. Notez que l’appel de Marcher.getVitesse() ou Marcher.this.getVitesse() n’aurait pas fonctionné. Un peu déroutant, nous le savons, mais vous devez être familier avec cette syntaxe pour l’examen.

Déclaration de Méthodes d’Interface static

Les interfaces sont également déclarées avec des méthodes static. Ces méthodes sont définies explicitement avec le mot-clé static et, pour la plupart, se comportent comme des méthodes static définies dans les classes.

Règles de Définition des Méthodes d’Interface Static

  1. Une méthode static doit être marquée avec le mot-clé static et inclure un corps de méthode.
  2. Une méthode static sans modificateur d’accès est implicitement public.
  3. Une méthode static ne peut pas être marquée abstract ou final.
  4. Une méthode static n’est pas héritée et ne peut pas être accédée dans une classe implémentant l’interface sans une référence au nom de l’interface.