50 votes

Pourquoi les classes enfant imbriquées peuvent-elles accéder aux membres privés de leur classe parent, mais pas les petits-enfants ?

Probablement similaire à la question, Pourquoi les classes Java externes peuvent-elles accéder aux membres privés des classes internes ? o Accès aux champs privés de la superclasse en utilisant le mot-clé super dans une sous-classe .

Mais il y a quelques différences : la classe enfant peut accéder aux membres privés de son parent (et de la classe seulement le parent le plus proche ).

Compte tenu de l'exemple de code ci-dessous :

public class T {

    private int t;

    class T1 {
        private int t1;

        public void test() {
            System.out.println(t);
        }
    }

    class T2 extends T1 {

        private int t2;

        public void test() {
            System.out.println(t);
            System.out.println(super.t1);
            System.out.println(this.t2);
        }
    }

    class T3 extends T2 {

        public void test() {
            System.out.println(t);
            System.out.println(super.t1); // NG: t1 Compile error! Why?
            System.out.println(super.t2); // OK: t2 OK
        }
    }
}

51voto

dimo414 Points 7128

Un exemple astucieux ! Mais c'est en fait une explication quelque peu ennuyeuse - il n'y a pas de problème de visibilité, vous n'avez simplement aucun moyen de vous référer à t1 directement de T3 parce que super.super n'est pas autorisé .

T2 ne peut pas accéder à ses propres t1 directement puisqu'il est privé (et les classes enfants n'héritent pas des champs privés de leurs parents), mais le champ super est effectivement une instance de T1 et comme c'est dans la même classe T2 peut faire référence aux champs privés de super . Il n'y a juste aucun mécanisme pour T3 pour aborder les champs privés de sa classe grand-parentale T1 directement.

Les deux compilent très bien à l'intérieur T3 qui démontre qu'une T3 peut accéder à l'interface de son grand-parent private champs :

System.out.println(((T1)this).t1);
System.out.println(new T1().t1);

A l'inverse, cela ne compile pas dans les deux cas T2 o T3 :

System.out.println(t1);

Si super.super étaient autorisées, vous pourriez le faire à partir de T3 :

System.out.println(super.super.t1);

si je définissais 3 classes, A , B , C , A ayant un champ protégé t1 y B hériterait de A y C de B , C pourrait faire référence à A s t1 en invoquant super.t1 parce qu'il est visible ici. logiquement, la même chose ne devrait-elle pas s'appliquer à l'héritage des classes internes, même si les champs sont privés, parce que ces membres privés devraient être visibles du fait de leur appartenance à la même classe ?

(Je vais m'en tenir à ce que dit l'OP. T1 , T2 y T3 noms de classe pour simplifier)

Si t1 étaient protected il n'y aurait aucun problème - T3 pourrait faire référence à la t1 directement comme n'importe quelle sous-classe. Le problème se pose avec private parce qu'une classe n'a pas sensibilisation de ses classes parentes private et ne peuvent donc pas les référencer directement, même s'ils sont visibles en pratique. C'est pourquoi vous devez utiliser super.t1 de T2 pour pouvoir faire référence au domaine en question.

Même si, dans la mesure où un T3 est concerné, il n'a pas t1 le champ auquel il a accès T1 s private en étant dans la même classe extérieure. Puisque c'est le cas, tout ce que vous avez à faire est de caster this à un T1 et vous avez un moyen de faire référence au champ privé. Le site super.t1 appeler T2 est (en substance) un casting this en un T1 Nous pouvons nous référer à ses champs.

15voto

charlie Points 245

Méthodes d'accès synthétiques

Techniquement, sur le JVM vous pouvez PAS accéder à tout private les membres d'une autre classe - ni ceux d'une classe englobante ( T.t ), ni celles d'une classe mère ( T2.t2 ). Dans votre code, il ressemble juste à vous pouvez, car le compilateur génère synthetic des méthodes d'accès pour vous dans les classes accédées. La même chose se produit lorsque dans la classe T3 classe, vous réparez la référence invalide super.t1 en utilisant la forme correcte ((T1) this).t1 .

Avec l'aide d'un tel compilateur généré synthetic vous pouvez en général accéder à cualquier private membre de cualquier imbriquée dans la classe externe (de premier niveau) T de la classe, par exemple de T1 vous pouvez utiliser new T2().t2 . Notez que cela s'applique à private static les membres aussi.

El synthetic a été introduit dans JDK version 1.1 pour prendre en charge les classes imbriquées, une nouvelle fonctionnalité du langage Java à cette époque. Depuis lors, le JLS permet explicitement l'accès mutuel à tous les membres d'une classe de premier niveau, y compris private les uns et les autres.

Mais pour des raisons de compatibilité ascendante, le compilateur déballe les classes imbriquées (par exemple pour T$T1 , T$T2 , T$T3 ) et traduit private membre accède à a appelle pour générer synthetic (ces méthodes doivent donc avoir l'attribut paquet privé c'est-à-dire par défaut la visibilité) :

class T {
    private int t;

    T() { // generated
        super(); // new Object()
    }

    static synthetic int access$t(T t) { // generated
        return t.t;
    }
}

class T$T1 {
    private int t1;

    final synthetic T t; // generated

    T$T1(T t) { // generated
        this.t = t;
        super(); // new Object()
    }

    static synthetic int access$t1(T$T1 t$t1) { // generated
            return t$t1.t1;
    }
}

class T$T2 extends T$T1 {
    private int t2;

    {
        System.out.println(T.access$t((T) this.t)); // t
        System.out.println(T$T1.access$t1((T$T1) this)); // super.t1
        System.out.println(this.t2);
    }

    final synthetic T t; // generated

    T$T2(T t) { // generated
        this.t = t;
        super(this.t); // new T1(t)
    }

    static synthetic int access$t2(T$T2 t$t2) { // generated
        return t$t2.t2;
    }
}

class T$T3 extends T$T2 {
    {
        System.out.println(T.access$t((T) this.t)); // t
        System.out.println(T$T1.access$t1((T$T1) this)); // ((T1) this).t1
        System.out.println(T$T2.access$t2((T$T2) this)); // super.t2 
    }

    final synthetic T t; // generated

    T$T3(T t) { // generated
        this.t = t;
        super(this.t); // new T2(t)
    }
}

N.B. : Vous n'êtes pas autorisé à vous référer à synthetic directement, donc dans le code source vous ne pouvez pas utiliser par exemple int i = T.access$t(new T()); vous-même.

7voto

Seelenvirtuose Points 5336

Très bonne découverte ! Je pense que nous avions tous supposé que votre exemple de code devait compiler.

Malheureusement, ce n'est pas le cas ... et la JLS nous donne une réponse dans §15.11.2. " Accès aux membres de la superclasse à l'aide de super " (c'est moi qui souligne) :

Supposons qu'une expression d'accès au champ super.f apparaisse dans la classe C, et que l'expression immédiat La superclasse de C est la classe S. Si f dans S est accessible depuis la classe C (§6.6), alors super.f est traitée comme si elle avait été l'expression this.f dans le corps de la classe S. Sinon, une erreur de compilation se produit.

L'accessibilité est donnée parce que tous les champs sont dans la même classe englobante. Ils peuvent être privés mais sont toujours accessibles.

Le problème est qu'en T2 (le immédiat superclasse de T3 ) le traitement de super.t1 comme this.t1 est illégal - il n'y a pas de champ t1 en T2 . D'où l'erreur du compilateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X