Pourquoi les classes Java externes peuvent-elles accéder aux membres privés de la classe interne?

178

J'ai observé que les classes externes peuvent accéder aux variables d'instance privée 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é?

Harish
la source
Cette question m'a dérouté pendant un certain temps jusqu'à ce que je vois le commentaire ... cela explique pourquoi je ne peux pas accéder à xx.x sur ma machine ..
Wang Sheng
4
Les commentaires m'ont confondu, j'exécute le code ci-dessus en Java 8, il compile et s'exécute. xx.x est accessible.
leon le
Harish, pouvez-vous s'il vous plaît désaccepter la réponse acceptée (qui ne répond pas à la question que vous avez posée) et accepter à la place la réponse de Martin Andersson ci-dessous, qui y répond très complètement?
temporary_user_name
FWIW cette syntaxe est absolument horrible:new ABC().new XYZ()
Josh M.

Réponses:

81

La classe interne est juste un moyen de séparer proprement certaines fonctionnalités qui appartiennent vraiment à la classe externe d'origine. Ils sont destinés à être utilisés lorsque vous avez 2 exigences:

  1. Certaines fonctionnalités de votre classe externe seraient plus claires si elles étaient implémentées dans une classe distincte.
  2. Même si c'est dans une classe distincte, la fonctionnalité est très étroitement liée à la façon dont la classe externe fonctionne.

Compte tenu de ces exigences, les classes internes ont un accès complet à leur classe externe. Puisqu'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 privates.

Kaleb Brasee
la source
221
Cette réponse explique pourquoi les classes imbriquées ont accès aux membres privés de leur classe externe. Mais la question est de savoir pourquoi la classe externe a accès aux membres privés des classes imbriquées.
Andrew
14
Ajoutez simplement "et vice versa" à Compte tenu de ces exigences, les classes internes ont un accès complet à leur classe externe et maintenant cela répond à la question.
anthropomo le
13
Celui-ci n'est pas la bonne réponse à ce problème, voici: stackoverflow.com/questions/19747812/…
Colin Di
4
@anthropomo: Non, ce n'est pas le cas. Les deux exigences sont parfaitement réalisables sans que la classe externe n'ait accès aux membres privés des classes internes.
OR Mapper
Un bon exemple où cela est particulièrement utile est le modèle Builder, stackoverflow.com/a/1953567/1262000 . La classe parent a juste besoin d'un constructeur qui prend un Builder et accède à toutes ses variables membres. Sinon, vous devrez avoir un constructeur privé dans la classe parente avec toutes les variables membres privées.
vikky.rk
62

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
    }
}
Ich
la source
Ceci est un brillant extrait de code. Juste ce dont j'avais besoin. Merci!
kevinarpe
Veuillez fournir le code qui pourrait être exécuté. De plus, veuillez indiquer la raison pour laquelle la variable privée n'a pas pu être autorisée à accéder.
androidyue
7
Mais alors ... la classe interne est anonyme. Vous ne pouvez pas créer plusieurs instances de cette classe interne, ou utiliser cette classe interne pour des déclarations de variables, etc.
OR Mapper
@OR Mapper c'est pourquoi même si xc'est public ici, ce n'est pas autorisé comme mMember.x.
MAC
56

La classe interne est (à des fins de contrôle d'accès) considérée comme faisant partie de la classe contenant. Cela signifie un accès complet à tous les privés.

La façon dont cela est implémenté utilise des méthodes synthétiques protégées par un package: la classe interne sera compilée dans une classe distincte dans le même package (ABC $ XYZ). La JVM ne prend pas en charge ce niveau d'isolation directement, de sorte qu'au niveau du bytecode ABC $ XYZ aura des méthodes protégées par package que la classe externe utilise pour accéder aux méthodes / champs privés.

Thilo
la source
17

Une réponse correcte apparaît sur une autre question similaire à celle-ci: Pourquoi le membre privé d'une classe imbriquée est-il accessible par les méthodes de la classe englobante?

Il dit 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 renferme la déclaration du membre ou du constructeur.

Colin Su
la source
Je pense que cet extrait peut bien répondre à ma question.
shen
5

Un cas d'utilisation important à mon humble avis pour les classes internes est le modèle d'usine. La classe englobante peut préparer une instance de la classe interne sans restrictions d'accès et transmettre l'instance au monde extérieur, où l'accès privé sera respecté.

Contrairement à abyx, déclarer la classe static ne modifie pas les restrictions d'accès à la classe englobante, comme indiqué ci-dessous. Les restrictions d'accès entre les classes statiques de la même classe englobante fonctionnent également. 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);
    }
}
Thomasfr
la source
1
Beau commentaire mais pas de réponse.
ceving le
@cerving C'est en fait la seule réponse qui m'a permis de voir une réelle utilisation pratique de cette décision de conception autrement bizarre. La question était de savoir pourquoi cela avait été décidé de cette manière, et c'est un excellent raisonnement - une démonstration de la différence entre ce à quoi vous pourriez souhaiter que la classe externe accède dans la classe interne et ce à quoi vous voulez que d'autres classes non liées aient accès.
et_l
4

Thilo a ajouté une bonne réponse à votre première question "Comment est-ce possible?". Je souhaite développer un peu la deuxième question posée: pourquoi ce comportement est-il autorisé?

Pour commencer, soyons parfaitement clairs sur le fait que ce comportement n'est pas limité aux 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 jusqu'à la déclaration suivante: le code imbriqué a un accès complet au code englobant - et vice versa.

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 haut. Si la région de l'amygdale de votre cerveau voit ce qu'il croit être une menace pour votre sécurité personnelle, disons une guêpe par exemple, il fera tourner votre corps dans l'autre sens et courra vers les collines sans que vous «y pensiez» deux fois.

Ainsi, le cerveau est une partie intrinsèque du corps - et curieusement, l'inverse aussi. L'utilisation du contrôle d'accès entre ces entités étroitement liées renonce à leur revendication de 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 de conduite pour d'autres études serait d'examiner comment un Java Iteratorest généralement implémenté.

L'accès illimité du code englobant au code imbriqué rend, pour la plupart, plutôt inutile d'ajouter des modificateurs d'accès aux champs et aux méthodes d'un type imbriqué. Cela ajoute de l'encombrement et pourrait donner un faux sentiment de sécurité aux nouveaux arrivants du langage de programmation Java.

Martin Andersson
la source
1
Cela devrait vraiment être la réponse acceptée. Si clair et complet. Au lieu de cela, la réponse acceptée ne répond même pas à la question.
temporary_user_name
OMI, cela ne répond toujours pas à la question de savoir pourquoi nous ne pouvons pas facilement ajouter des champs privés à une classe interne, à laquelle la classe externe ne peut pas accéder directement. À moins que je ne me trompe, cela ruine l'un des principaux cas pour les classes internes - la création de types immuables de courte durée "structur-like". FWIW C # soutient volontiers ceci: repl.it/repls/VengefulCheeryInverse
Josh M.
3

Les restrictions d'accès sont effectuées par classe. Il n'existe aucun moyen pour une méthode déclarée dans une classe de ne pas pouvoir accéder à tous les membres de l'instance / de la classe. Cela va de soi que les classes internes ont également un accès sans entrave aux membres de la classe externe, et que la classe externe a un accès sans entrave aux membres de la classe interne.

En plaçant une classe dans une autre classe, vous la rendez étroitement liée à l'implémentation, et tout ce qui fait partie de l'implémentation doit avoir accès aux autres parties.

TofuBière
la source
3

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 auront besoin de partager quelques choses, et il est donc logique qu'elles puissent avoir plus de flexibilité que les classes «régulières».

Si, dans votre cas, cela n'a aucun sens pour les classes d'être capables de voir le fonctionnement interne de l'autre - ce qui signifie essentiellement que la classe interne aurait simplement pu être transformée en classe régulière, vous pouvez déclarer la classe interne comme static class XYZ. L'utilisation staticsignifiera qu'ils ne partageront pas l'état (et, par exemple new ABC().new XYZ(), ne fonctionnera pas, et vous devrez l'utiliser new ABC.XYZ().
Mais, si c'est le cas, vous devriez vous demander si cela XYZdevrait vraiment être une classe interne et que peut-être qu'elle mérite la sienne 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 que votre classe externe utilise, et cela ne sera utile nulle part ailleurs). Mais à peu près la moitié du temps il aurait dû devenir une classe extérieure.

abyx
la source
2
La classe externe peut également accéder aux membres privés des classes internes statiques , cela n'a donc rien à voir avec static . Vous dites "cela n'a aucun sens pour les classes d'être capables de voir le fonctionnement interne de l'autre", mais ce n'est pas nécessairement le cas - et si cela avait du sens juste pour la classe interne de voir le fonctionnement interne de la classe externe, mais pas le vice - versa?
OR Mapper
-1

La classe interne est considérée comme un attribut de la classe externe. Par conséquent, peu importe que la variable d'instance de la classe interne soit privée ou non, la classe externe peut y accéder sans aucun problème, tout comme accéder à ses autres attributs privés (variables).

class Outer{

private int a;

class Inner{
private int b=0;
}

void outMethod(){
a = new Inner().b;
}
}
Singe de lune
la source
-2

Parce que votre main()méthode est dans la ABCclasse, qui peut accéder à sa propre classe interne.

aberrant80
la source
2
La question n'est pas de savoir si les membres de la ABCclasse peuvent accéder aux classes imbriquées dans la ABCclasse, mais pourquoi ils peuvent accéder aux membres privés des classes imbriquées dans la ABCclasse en Java.
OR Mapper
J'ai répondu à la question le jour même où elle a été posée. Quelqu'un a édité la question 2 ans plus tard, et le vote défavorable est intervenu 3 ans plus tard. Je suis presque sûr que celui qui a édité la question a trop changé le libellé de la question.
aberrant80
@ aberrant80 L' historique des révisions est public, et même la question originale demande clairement pourquoi .
nog642