J'ai observé que les classes externes peuvent accéder aux variables d'instance privées des classes internes. Comment est-ce possible? Voici un exemple de code démontrant la même chose:
class ABC{
class XYZ{
private int x=10;
}
public static void main(String... args){
ABC.XYZ xx = new ABC().new XYZ();
System.out.println("Hello :: "+xx.x); ///Why is this allowed??
}
}
Pourquoi ce comportement est-il autorisé?
La classe interne n'est qu'un moyen de séparer proprement certaines fonctionnalités qui appartiennent réellement à la classe externe d'origine. Ils sont destinés à être utilisés lorsque vous avez 2 exigences:
Compte tenu de ces exigences, les classes internes ont un accès complet à leur classe externe. Comme ils sont fondamentalement membres de la classe externe, il est logique qu'ils aient accès aux méthodes et attributs de la classe externe - y compris les classes privées.
Si vous souhaitez masquer les membres privés de votre classe interne, vous pouvez définir une interface avec les membres publics et créer une classe interne anonyme qui implémente cette interface. Exemple ci-dessous:
class ABC{
private interface MyInterface{
void printInt();
}
private static MyInterface mMember = new MyInterface(){
private int x=10;
public void printInt(){
System.out.println(String.valueOf(x));
}
};
public static void main(String... args){
System.out.println("Hello :: "+mMember.x); ///not allowed
mMember.printInt(); // allowed
}
}
La classe interne est (aux fins du contrôle d'accès) considérée comme faisant partie de la classe contenue. Cela signifie un accès complet à tous les soldats.
Cette méthode est mise en œuvre à l'aide de méthodes synthétiques protégées par package: la classe interne sera compilée dans une classe distincte du même package (ABC $ XYZ). La machine virtuelle Java ne prend pas directement en charge ce niveau d'isolation, de sorte qu'au niveau du bytecode, ABC $ XYZ disposera de méthodes protégées par le paquet que la classe externe utilisera pour accéder aux méthodes/champs privés.
Une réponse correcte apparaît sur une autre question similaire à celle-ci: Pourquoi le membre privé d'une classe imbriquée peut-il être accédé par les méthodes de la classe englobante?
Il indique qu'il existe une définition de la portée privée sur JLS - Détermination de l'accessibilité :
Sinon, si le membre ou le constructeur est déclaré privé, , l'accès est autorisé si et seulement s'il se produit dans le corps de la classe de niveau supérieur (§7.6) qui inclut la déclaration du membre ou constructeur.
Le modèle d’usine est un exemple important d’utilisation pour les classes internes. La classe englobante peut préparer une instance de la classe interne sans restrictions d'accès et la transmettre au monde extérieur, où l'accès privé sera honoré.
Contrairement à abyx , déclarer la classe statique ne modifie pas les restrictions d'accès à la classe englobante, comme indiqué ci-dessous. De plus, les restrictions d'accès entre les classes statiques d'une même classe englobante fonctionnent. J'ai été surpris ...
class MyPrivates {
static class Inner1 { private int test1 = 2; }
static class Inner2 { private int test2 = new Inner1().test1; }
public static void main(String[] args) {
System.out.println("Inner : "+new Inner2().test2);
}
}
Les restrictions d'accès sont définies par classe. Il est impossible pour une méthode déclarée dans une classe de ne pas pouvoir accéder à tous les membres d'instance/de classe. Il va sans dire que les classes internes ont également un accès illimité aux membres de la classe externe et que la classe externe a un accès sans restriction aux membres de la classe interne.
En plaçant une classe dans une autre classe, vous l'associez étroitement à l'implémentation. Tout ce qui en fait partie devrait avoir accès aux autres parties.
La logique derrière les classes internes est que si vous créez une classe interne dans une classe externe, c'est parce qu'elles devront partager quelques choses, et il est donc logique qu'elles puissent avoir plus de flexibilité que les classes "normales".
Si, dans votre cas, il n’a aucun sens de voir les classes en mesure de voir le fonctionnement interne des autres - ce qui signifie fondamentalement que la classe interne pourrait tout simplement être devenue une classe régulière, vous pouvez déclarer la classe interne comme static class XYZ
. Utiliser static
signifie qu'ils ne partageront pas l'état (et, par exemple, new ABC().new XYZ()
ne fonctionnera pas, et vous devrez utiliser new ABC.XYZ()
.
Mais si c'est le cas, vous devriez vous demander si XYZ
devrait vraiment être une classe interne et qu'il mérite peut-être son propre fichier. Parfois, il est judicieux de créer une classe interne statique (par exemple, si vous avez besoin d'une petite classe qui implémente une interface utilisée par votre classe externe et qui ne sera utile nulle part ailleurs). Mais à peu près à la moitié du temps, il aurait fallu en faire une classe extérieure.
Thilo a ajouté un bon réponse pour votre première question "Comment est-ce possible?". Je voudrais élaborer un peu sur la deuxième question posée: pourquoi ce comportement est-il autorisé?
Pour commencer, il est clair que ce comportement n'est pas seulement autorisé pour les classes internes qui, par définition, sont des types imbriqués non statiques. Ce comportement est autorisé pour tous les types imbriqués, y compris les énumérations imbriquées et les interfaces qui doivent être statiques et ne peuvent pas avoir d'instance englobante. Fondamentalement, le modèle est une simplification allant jusqu'à l'instruction suivante: Le code imbriqué a un accès complet au code inclusif - et inversement.
Alors pourquoi alors? Je pense qu'un exemple illustre mieux ce point.
Pensez à votre corps et à votre cerveau. Si vous injectez de l'héroïne dans votre bras, votre cerveau devient plus haut. Si la région de l'amygdale de votre cerveau voit ce qu'il croit être une menace pour votre sécurité personnelle, par exemple une guêpe, il fera tourner votre corps dans l'autre sens et courra vers les collines sans que vous ne pensiez à ce sujet.
Ainsi, le cerveau est une partie intrinsèque du corps - et étrangement, l’inverse aussi. L'utilisation du contrôle d'accès entre des entités aussi étroitement liées perd leur droit à une relation. Si vous avez besoin d'un contrôle d'accès, vous devez séparer davantage les classes en unités vraiment distinctes. Jusque-là, ils sont la même unité. Un exemple probant pour des études ultérieures serait de regarder comment Java Iterator
est généralement implémenté.
L'accès illimité du code englobant au code imbriqué rend, dans la plupart des cas, plutôt inutile d'ajouter des modificateurs d'accès aux champs et aux méthodes d'un type imbriqué. Cela créerait un fouillis et pourrait donner un faux sentiment de sécurité aux nouveaux venus du langage de programmation Java).