web-dev-qa-db-fra.com

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 externes Java accèdent-elles aux membres privés de la classe interne? ou 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 enfants peut accéder aux membres privés de leur parent (et uniquement la classe parent la plus proche ).

Étant donné 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
        }
    }
}
50
andyf

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

T2 ne peut pas accéder à son propre t1 champ directement car il est privé (et les classes enfants n'héritent pas des champs privés de leurs parents), mais super est en fait une instance de T1 et puisque c'est dans la même classe T2 peut faire référence aux champs privés de super. Il n'y a tout simplement pas de mécanisme pour T3 pour adresser les champs privés de sa classe de grands-parents T1 directement.

Les deux compilent très bien à l'intérieur T3, ce qui démontre qu'un T3 peut accéder aux champs private de ses grands-parents:

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

Inversement, cela ne se compile ni dans T2 ou T3:

System.out.println(t1);

Si super.super ont été autorisés à 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 et B hériteraient de A et C de B, C pourraient faire référence à As t1 en appelant super.t1 car il est visible ici. logiquement, la même chose ne devrait-elle pas s'appliquer à l'héritage des classes internes même si le champ est privé, car ces membres privés devraient être visibles car ils sont dans la même classe?

(Je vais m'en tenir à l'OP T1, T2, et T3 noms de classe pour plus de simplicité)

Si t1 étaient protected il n'y aurait aucun problème - T3 pourrait faire référence à t1 champ directement comme n'importe quelle sous-classe. Le problème se pose avec private car une classe n'a pas conscience des champs private de ses classes parentes et peut donc 'ne les référencez pas directement, même si en pratique ils sont visibles. C'est pourquoi vous devez utiliser super.t1 de T2, pour se référer même au domaine en question.

Même si jusqu'à T3 craint de ne pas avoir t1 champ auquel il a accès T1s private champs en étant dans la même classe externe. Puisque c'est le cas, tout ce que vous devez faire est de convertir this en T1 et vous avez un moyen de vous référer au champ privé. Le super.t1 appelle T2 transforme (essentiellement) this en T1 en nous référant à ses domaines.

50
dimo414

Méthodes d'accessoires sythétiques

Techniquement, au niveau [~ # ~] jvm [~ # ~], vous pouvez [~ # ~] pas [~ # ~] accéder aux private membres d'une autre classe - ni ceux d'une classe englobante (T.t), ni ceux d'une classe parente (T2.t2). Dans votre code, il ressemble à vous pouvez, car le compilateur génère synthetic méthodes d'accesseur pour vous dans les classes accédées. La même chose se produit lorsque dans la classe T3 Vous corrigez la référence non valide super.t1 en utilisant le bon formulaire ((T1) this).t1.

Avec l'aide d'un tel compilateur généré synthetic méthode d'accesseur, vous pouvez en général accéder à private membre de toute classe imbriquée dans la couche externe (niveau supérieur) T classe, par exemple à partir de T1, vous pouvez utiliser new T2().t2. Notez que cela s'applique également aux membres private static.

L'attribut synthetic a été introduit dans [~ # ~] jdk [~ # ~] 1.1 pour prendre en charge les classes imbriquées, une nouvelle fonctionnalité de langage dans Java à cette époque. Depuis lors, le [~ # ~] jls [~ # ~] permet explicitement accès mutuel à tous les membres d'une classe de niveau supérieur, y compris private ceux.

Mais pour des raisons de compatibilité ascendante, le compilateur déballe les classes imbriquées (par exemple en T$T1, T$T2, T$T3) Et traduit private membre accède aux appels aux appels générés synthetic méthodes d'accesseur (ces méthodes doivent donc avoir le package privé, c'est-à-dire par défaut, 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 directement aux membres synthetic, donc dans le code source, vous ne pouvez pas utiliser par exemple int i = T.access$t(new T()); vous-même.

15
charlie

Très bonne trouvaille! Je pense que nous avions tous supposé que votre exemple de code devrait être compilé.

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

Supposons qu'une expression d'accès au champ super.f apparaît dans la classe C, et la immédiate superclasse de C est la classe S. Si f dans S est accessible depuis la classe C (§6.6), alors super.f est traité comme s'il s'agissait de l'expression this.f dans le corps de la classe S. Sinon, une erreur de compilation se produit.

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

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

7
Seelenvirtuose