Comment fonctionnent les modificateurs d’accès en Java?

Vous avez déjà vu qu’il existe quatre modificateurs d’accès : privé (private), package, protégé (protected), et public (public). Nous allons les discuter dans l’ordre du plus restrictif au moins restrictif :

  • private : Accessible uniquement au sein de la même classe.
  • Accès package : private plus les autres membres du même package. Parfois appelé package-private ou accès par défaut.
  • protected : Accès package plus accès au sein des sous-classes.
  • public : protected plus les classes dans les autres packages.

Nous allons explorer l’impact de ces quatre niveaux d’accès sur les membres d’une classe.

Accès Privé

Commençons par l’accès privé, qui est le plus simple. Seul le code dans la même classe peut appeler des méthodes privées ou accéder à des champs privés.

Voici un code parfaitement légal parce que tout est dans une classe :

package etang.canard;
public class PereDuck {
    private String bruit = "coin";
    private void coinCoin() {
        System.out.print(bruit); // l'accès privé est ok
    }
}

Jusqu’ici, tout va bien. PereDuck déclare une méthode privée coinCoin() et utilise la variable d’instance privée bruit à la ligne 5.

Maintenant, ajoutons une autre classe :

package etang.canard;
public class MauvaisCanneton {
    public void faireDuBruit() {
        var canard = new PereDuck();
        canard.coinCoin();           // NE COMPILE PAS
        System.out.print(canard.bruit); // NE COMPILE PAS
    }
}

MauvaisCanneton essaie d’accéder à une variable d’instance et une méthode qu’il n’a pas le droit de toucher. À la ligne 5, il tente d’accéder à une méthode privée dans une autre classe. À la ligne 6, il tente d’accéder à une variable d’instance privée dans une autre classe. Les deux génèrent des erreurs de compilation. Mauvais canneton !

Notre mauvais canneton n’a que quelques jours et ne sait pas encore mieux. Heureusement, vous savez que l’accès aux membres privés d’autres classes n’est pas autorisé, et vous devez utiliser un type d’accès différent.

Dans l’exemple précédent, PereDuck et MauvaisCanneton sont dans des fichiers séparés, mais que se passerait-il s’ils étaient déclarés dans le même fichier ? Même dans ce cas, le code ne compilerait toujours pas car Java empêche l’accès en dehors de la classe.

Accès Package

Heureusement, MereDuck est plus accommodante quant à ce que ses cannetons peuvent faire. Elle permet aux classes du même package d’accéder à ses membres. Quand il n’y a pas de modificateur d’accès, Java suppose l’accès package.

package etang.canard;
public class MereDuck {
    String bruit = "coin";
    void coinCoin() {
        System.out.print(bruit); // l'accès package est ok
    }
}

MereDuck peut se référer à bruit et appeler coinCoin(). Après tout, les membres de la même classe sont certainement dans le même package. La grande différence est que MereDuck permet à d’autres classes du même package d’accéder aux membres, tandis que PereDuck ne le fait pas (car ils sont privés).

BonCanneton a une bien meilleure expérience que MauvaisCanneton :

package etang.canard;
public class BonCanneton {
    public void faireDuBruit() {
        var canard = new MereDuck();
        canard.coinCoin();           // l'accès package est ok
        System.out.print(canard.bruit); // l'accès package est ok
    }
}

BonCanneton réussit à apprendre à faire coinCoin() et à faire du bruit en copiant sa mère. Notez que toutes les classes couvertes jusqu’à présent sont dans le même package, etang.canard. Cela permet à l’accès package de fonctionner.

Dans ce même étang, un cygne vient de donner naissance à un bébé cygne. Un bébé cygne s’appelle un cygnet. Le cygnet voit les cannetons apprendre à faire coin-coin et décide d’apprendre de MereDuck aussi.

package etang.cygne;
import etang.canard.MereDuck; // importer un autre package
public class MauvaisCygnet {
    public void faireDuBruit() {
        var canard = new MereDuck();
        canard.coinCoin();           // NE COMPILE PAS
        System.out.print(canard.bruit); // NE COMPILE PAS
    }
}

Oh non ! MereDuck n’autorise les leçons qu’aux autres canards en restreignant l’accès au package etang.canard. Le pauvre petit MauvaisCygnet est dans le package etang.cygne, et le code ne compile pas. Rappelez-vous que lorsqu’il n’y a pas de modificateur d’accès sur un membre, seules les classes du même package peuvent accéder au membre.

Accès Protégé

L’accès protégé permet tout ce que l’accès package permet, et plus encore. Le modificateur d’accès protected ajoute la capacité d’accéder aux membres d’une classe parente. Nous couvrons la création de sous-classes en profondeur plus tard. Pour l’instant, nous couvrons l’utilisation la plus simple possible d’une sous-classe. Dans l’exemple suivant, la classe “enfant” PoissonClown est une sous-classe de la classe “parent” Poisson, en utilisant le mot-clé extends pour les connecter :

public class Poisson {}
public class PoissonClown extends Poisson {}

En étendant une classe, la sous-classe gagne accès à tous les membres protected et public de la classe parente, comme s’ils étaient déclarés dans la sous-classe. Si les deux classes sont dans le même package, alors la sous-classe gagne également accès à tous les membres package.

D’abord, créons une classe Oiseau et donnons un accès protégé à ses membres :

package etang.rivage;
public class Oiseau {
    protected String texte = "flottant";
    protected void flotterDansEau() {
        System.out.print(texte); // l'accès protégé est ok
    }
}

Ensuite, nous créons une sous-classe :

package etang.oie; // Package différent de Oiseau
import etang.rivage.Oiseau;
public class Oison extends Oiseau { // Oison est une sous-classe d'Oiseau
    public void nager() {
        flotterDansEau();          // l'accès protégé est ok
        System.out.print(texte);   // l'accès protégé est ok
    }
    public static void main(String[] args) {
        new Oison().nager();
    }
}

C’est une sous-classe simple. Elle étend la classe Oiseau. L’extension signifie créer une sous-classe qui a accès à tous les membres protected ou public de la classe parente. L’exécution de ce programme imprime “flottant” deux fois : une fois en appelant flotterDansEau(), et une fois depuis l’instruction print dans nager(). Puisque Oison est une sous-classe d’Oiseau, elle peut accéder à ces membres même si elle est dans un package différent.

Rappelez-vous que protected nous donne également accès à tout ce que l’accès package permet. Cela signifie qu’une classe dans le même package que Oiseau peut accéder à ses membres protégés.

package etang.rivage; // Même package qu'Oiseau
public class ObservateurOiseau {
    public void regarderOiseau() {
        Oiseau oiseau = new Oiseau();
        oiseau.flotterDansEau();    // l'accès protégé est ok
        System.out.print(oiseau.texte); // l'accès protégé est ok
    }
}

Puisque Oiseau et ObservateurOiseau sont dans le même package, ObservateurOiseau peut accéder aux membres package de la variable oiseau. La définition de protected permet l’accès aux sous-classes et aux classes dans le même package. Cet exemple utilise la partie de la définition qui concerne le même package.

Maintenant, essayons la même chose depuis un package différent :

package etang.interieur; // Package différent d'Oiseau
import etang.rivage.Oiseau;
public class ObservateurOiseauDeLoin { // Pas une sous-classe d'Oiseau
    public void regarderOiseau() {
        Oiseau oiseau = new Oiseau();
        oiseau.flotterDansEau();    // NE COMPILE PAS
        System.out.print(oiseau.texte); // NE COMPILE PAS
    }
}

ObservateurOiseauDeLoin n’est pas dans le même package qu’Oiseau, et il n’hérite pas d’Oiseau. Cela signifie qu’il n’est pas autorisé à accéder aux membres protected d’Oiseau.

Il y a un piège pour l’accès protégé. Considérez cette classe :

package etang.cygne; // Package différent d'Oiseau
import etang.rivage.Oiseau;
public class Cygne extends Oiseau { // Cygne est une sous-classe d'Oiseau
    public void nager() {
        flotterDansEau();          // l'accès protégé est ok
        System.out.print(texte);   // l'accès protégé est ok
    }
    public void aiderAutreCygneANager() {
        Cygne autre = new Cygne();
        autre.flotterDansEau();    // accès sous-classe à superclasse
        System.out.print(autre.texte); // accès sous-classe à superclasse
    }
    public void aiderAutreOiseauANager() {
        Oiseau autre = new Oiseau();
        autre.flotterDansEau();    // NE COMPILE PAS
        System.out.print(autre.texte); // NE COMPILE PAS
    }
}

Prenez une profonde respiration. C’est intéressant. Cygne n’est pas dans le même package qu’Oiseau mais l’étend — ce qui implique qu’il a accès aux membres protected d’Oiseau puisqu’il s’agit d’une sous-classe. Et c’est le cas. Les lignes 5 et 6 se réfèrent aux membres protégés via l’héritage.

Les lignes 10 et 11 utilisent également avec succès les membres protected d’Oiseau. C’est autorisé car ces lignes se réfèrent à un objet Cygne. Cygne hérite d’Oiseau, donc c’est correct. C’est une sorte de vérification en deux phases. La classe Cygne est autorisée à utiliser les membres protected d’Oiseau, et nous nous référons à un objet Cygne. Certes, c’est un objet Cygne créé à la ligne 9 plutôt qu’un objet hérité, mais c’est toujours un objet Cygne.

Les lignes 15 et 16 ne compilent pas. Attendez une minute. Elles sont presque exactement les mêmes que les lignes 10 et 11 ! Il y a une différence clé. Cette fois, une référence Oiseau est utilisée plutôt que l’héritage. Elle est créée à la ligne 14. Oiseau est dans un package différent, et ce code n’hérite pas d’Oiseau, donc il n’a pas le droit d’utiliser les membres protected. Comment ça ? Nous venons de dire à plusieurs reprises que Cygne hérite d’Oiseau. Et c’est vrai. Cependant, la référence de variable n’est pas un Cygne. Le code se trouve simplement dans la classe Cygne.

C’est normal d’être confus. C’est sans doute l’un des points les plus déroutants. En regardant d’une manière différente, les règles protected s’appliquent dans deux scénarios :

  • Un membre est utilisé sans se référer à une variable. C’est le cas aux lignes 5 et 6. Dans ce cas, nous profitons de l’héritage, et l’accès protected est autorisé.
  • Un membre est utilisé via une variable. C’est le cas aux lignes 10, 11, 15, et 16. Dans ce cas, les règles pour le type de référence de la variable sont ce qui compte. S’il s’agit d’une sous-classe, l’accès protected est autorisé. Cela fonctionne pour les références à la même classe ou à une sous-classe.

Nous allons essayer à nouveau pour vous assurer que vous comprenez ce qui se passe. Pouvez-vous comprendre pourquoi ces exemples ne compilent pas ?

package etang.oie;
import etang.rivage.Oiseau;
public class Oie extends Oiseau {
    public void aiderOieANager() {
        Oie autre = new Oie();
        autre.flotterDansEau();
        System.out.print(autre.texte);
    }
    public void aiderAutreOieANager() {
        Oiseau autre = new Oie();
        autre.flotterDansEau();    // NE COMPILE PAS
        System.out.print(autre.texte); // NE COMPILE PAS
    }
}

La première méthode est correcte. En fait, elle est équivalente à l’exemple du Cygne. Oie étend Oiseau. Puisque nous sommes dans la sous-classe Oie et que nous nous référons à une référence Oie, elle peut accéder aux membres protected. La deuxième méthode pose problème. Bien que l’objet soit en fait une Oie, il est stocké dans une référence Oiseau. Nous ne sommes pas autorisés à nous référer aux membres de la classe Oiseau puisque nous ne sommes pas dans le même package et que le type de référence de autre n’est pas une sous-classe d’Oie.

Et celui-ci ?

package etang.canard;
import etang.oie.Oie;
public class ObservateurOie {
    public void regarder() {
        Oie oie = new Oie();
        oie.flotterDansEau();    // NE COMPILE PAS
    }
}

Ce code ne compile pas car nous ne sommes pas dans l’objet oie. La méthode flotterDansEau() est déclarée dans Oiseau. ObservateurOie n’est pas dans le même package qu’Oiseau, et il n’étend pas non plus Oiseau. Oie étend Oiseau. Cela permet seulement à Oie de se référer à flotterDansEau(), pas aux appelants de Oie.

Si cela vous déroute encore, essayez-le. Tapez le code et essayez de le faire compiler. Puis relisez cette section. Ne vous inquiétez pas — ce n’était pas évident pour nous non plus la première fois !

Accès Public

L’accès protégé était un concept difficile. Heureusement, le dernier type de modificateur d’accès est facile : public signifie que n’importe qui peut accéder au membre de n’importe où.

Le système de modules Java redéfinit “n’importe où”, et il devient possible de restreindre l’accès au code public en dehors d’un module. Nous couvrons cela plus en détail dans le Chapitre 12, “Modules”. Lorsque des exemples de code sont donnés, vous pouvez supposer qu’ils sont dans le même module sauf indication contraire.

Créons une classe qui a des membres publics :

package etang.canard;
public class ProfesseurCanard {
    public String nom = "serviable";
    public void nager() {
        System.out.print(nom);      // l'accès public est ok
    }
}

ProfesseurCanard permet l’accès à n’importe quelle classe qui le souhaite. Maintenant, nous pouvons l’essayer :

package etang.oie;
import etang.canard.ProfesseurCanard;
public class CannetonPerdu {
    public void nager() {
        var professeur = new ProfesseurCanard();
        professeur.nager();         // autorisé
        System.out.print("Merci" + professeur.nom); // autorisé
    }
}

CannetonPerdu peut se référer à nager() et nom sur ProfesseurCanard car ils sont public. L’histoire a une fin heureuse. CannetonPerdu a appris à nager et peut retrouver ses parents — tout cela parce que ProfesseurCanard a rendu les membres public.

Révision des Modificateurs d’Accès

Assurez-vous de savoir pourquoi tout dans le Tableau 5.4 est vrai. Utilisez la première colonne pour le premier blanc et la première ligne pour le second blanc. Aussi, rappelez-vous qu’un membre est une méthode ou un champ.

 privatepackageprotectedpublic
la même classeOuiOuiOuiOui
une autre classe dans le même packageNonOuiOuiOui
une sous-classe dans un package différentNonNonOuiOui
une classe sans relation dans un package différentNonNonNonOui