Impossible d'accéder à la variable privée de sa propre classe via une instance de sous-classe

class A {
    private int foo;
    void bar(B b) { b.foo = 42; }
}

class B extends A { }

Cela ne parvient pas à compiler avec l'erreur:

A.java:3: error: foo has private access in A
    void bar(B b) { b.foo = 42; }
                     ^
1 error

L'ajout d'un cast à la classe de base le fait fonctionner.

void bar(B b) { ((A) b).foo = 42; }

Quelqu'un peut-il m'indiquer pourquoi le premier extrait est illégal? Quelle est la raison pour laquelle c'est interdit? Voici ce que dit le JLS :

Sinon, le membre ou le constructeur est déclaré private, et 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 entoure la déclaration du membre ou constructeur.

Du mieux que je peux dire, mon code répond à cette formulation. Est-ce donc un bug avec le compilateur Java, ou mon interprétation du JLS est-elle incorrecte?

(Note: Je ne cherche pas de solutions de contournement, comme faire la variable protected. Je sais comment contourner ce problème.)

24
demandé sur John Kugelman 2015-02-07 03:20:43

7 réponses

Le message D'erreur "a un accès privé dans A" est un bug java depuis très très longtemps.

JDK 1.1:

JDK-4096353 : JLS 6.6.1: lorsque les références de sous-classe sont utilisées pour accéder aux parties privées des superclasses

Contient un extrait de code conforme exactement à la question

class X{
  private static int i = 10;
  void f()     {
    Y oy = new  Y();
    oy.i = 5;  // Is this an error? Is i accessable through a reference to Y?
  }
}
class Y extends X {}

Ils ont essayé de le réparer et cela conduit à

JDK-4122297 : les messages d'erreur de javac ne sont pas appropriés pour champ.

======TP1======
1  class C extends S {
2      void f(){
3          java.lang.System.out.println("foo");
4      }
5  }
6
7  class S {
8      private int java;
9  }
======
% javac C.java
C.java:3: Variable java in class S not accessible from class C.
    java.lang.System.out.println("foo");
    ^
C.java:3: Attempt to reference field lang in a int.
   java.lang.System.out.println("foo");
       ^
2 errors
======

Mais par spécification java n'est pas hérité en C et ce programme devrait compiler.

Il a été corrigé en 1.2, mais apparaît à nouveau en 1.3

JDK-4240480 : name00705.html: jls6. 3 les membres privés ne doivent pas être hérités des superclasses

JDK-4249653 : new javac suppose que les champs privés sont hérités par une sous-classe

Et quand les génériques viennent

JDK-6246814 : Membre privé de type variable injustement accessible

JDK-7022052 : erreur du compilateur invalide sur la méthode privée et les génériques


Cependant, par le JLS, ce membre n'existe tout simplement pas dans le type hérité.

JLS 8.2. Membres Du Groupe

Les membres d'une classe qui sont déclarés privés ne sont pas hérités par les sous-classes de cette classe.

, Donc b.foo est illégal parce que la classe B n'a pas de champ nommé foo. Il est aucune restriction, c'est un champ absent dans B.

Java a un typage fort et nous ne pouvons pas accéder aux champs qui n'existent pas dans B même s'ils existent dans la superclasse A.

Cast (A) b est légal, car B est une sous-classe de A.

A a un champ nommé foo et on peut accéder à ce champ privé parce que b(B b) est une fonction de classe A, même si b != this en raison de

JLS 6.6.1. Déterminer L'Accessibilité

Sinon, si le le membre ou le constructeur est déclaré privé, alors 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 entoure la déclaration du membre ou du constructeur.

Aussi si nous écrivons

class A {
  private int foo;
  void baz(A b) { b.foo = 42; }
}

class B extends A { }

class T {
  void x() {
    B b = new B();
    b.baz(b);
  }
}

Il va compiler car Java déduit des arguments de type pour les appels polymorphes.

JLS 15.12.2.7. Déduire des Arguments de Type basés sur des Arguments réels:

Une contrainte de supertype T: > X implique que la solution est un des supertypes de X. étant donné plusieurs de ces contraintes sur T, nous pouvons croiser les ensembles de supertypes impliqués par chacune des contraintes, puisque le paramètre type doit être membre de tous. Nous pouvons alors choisir le type le plus spécifique qui se trouve dans l'intersection

16
répondu Nikolay 2015-02-12 19:13:15

Vous ne pouvez pas dire b.foo parce que foo est privé et ne sera donc pas hérité, par conséquent la classe B ne peut pas voir la variable foo et n'est pas au courant si une variable nommée foo existe même-à moins qu'elle ne soit marquée protégée (comme vous l'avez dit) ou par défaut (comme

Si vous souhaitez utiliser foo sans l'aide d'un cast explicite comme votre deuxième exemple, vous devez utiliser this.foo ou simplement foo, qui a un implicite this. CommeJavadocs spécifié, le this mot clé raison principale est d'empêcher que:

La raison la plus courante pour utiliser le mot-clé this est qu'un champ est ombré par une méthode ou un paramètre constructeur.

Lorsque vous avez utilisé ((A) b), vous lancez le type de référence et le compilateur va le voir comme si vous utilisez un A type de variable de référence, en d'autres termes, quelque chose comme A a, et a.foo est totalement légal.

Un résumé illustré de la visibilité et de l'accès aux superclasses variables d'instance privée: ici

13
répondu Tarik 2015-02-11 22:17:13

La spécification pour les expressions d'accès aux champs, chapitre 15.11 dit:

Si l'identifiant ne nomme pas un champ Membre accessible dans le type T, ensuite, l'accès au champ n'est pas défini et une erreur de compilation se produit.

Du point de vue de la super classe, en regardant les types, je dirais que le membre est non accessible, d'où l'erreur.

Je pense que le cas que vous présentez est plus proche de l'accès à un membre en tant que champ, qui est montré dans l'exemple 15.11-1-1.

class S           { int x = 0; }
class T extends S { int x = 1; }
class Test1 {
    public static void main(String[] args) {
        T t = new T();
        System.out.println("t.x=" + t.x + when("t", t));
        S s = new S();
        System.out.println("s.x=" + s.x + when("s", s));
        s = t;
        System.out.println("s.x=" + s.x + when("s", s));
    }
    static String when(String name, Object t) {
        return " when " + name + " holds a "
                        + t.getClass() + " at run time.";
    }
}

Juste pour répondre à votre question:

Veuillez expliquer quel type de mauvais code la restriction protège contre.

Veuillez considérer l'extrait suivant.

public class X {
    private int a;

    public void bar(Z z) {
        z.a // not visible, but if was, what 'a' 
            // would you actually access at this point 'X' or 'Z'
    }
}

public class Z extends X {
    private int a;
}
2
répondu lpiepiora 2015-02-10 07:27:19

Il me semble que la spécification est incohérente. Comme l'indique John, le corps des États spec

Sinon, le membre ou le constructeur est déclaré privé, et 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 entoure la déclaration du membre ou du constructeur.

Et il n'y a aucune mention de sous-classes. Donc, la classe A devrait compiler correctement. Cependant, l'exemple 6.6-5 indique

Un membre de la classe privée ou constructor est accessible uniquement dans le corps de la classe de niveau supérieur (§7.6) qui contient la déclaration du membre ou du constructeur. il n'est pas hérité par les sous-classes .

Cette deuxième instruction est à la fois plus faible (non seulement si), mais apporte des sous-classes à la table. Selon cette Un ne devrait pas compiler.

1
répondu Neil Masson 2015-02-13 15:14:08

Java est pointilleux sur l'accès aux variables privées via un type de référence qui ne devrait pas avoir accès à cette variable. Vous devriez être capable de le faire légalement en écrivant ((A) b).foo = 42.

0
répondu Louis Wasserman 2015-02-07 00:23:28

Nous ne pouvons pas hériter des champs ou des méthodes private. Donc, dans votre classe de code B est complètement ignorant de la variable foo même si vous accédez à partir de sa propre classe A.

-1
répondu Anesh 2015-02-10 06:18:13

N'est-ce pas à quoi sert le modificateur d'accès par défaut?

Essayez ceci :

public class blah{
    static class A {
        int foo;
        void bar(B b) {b.foo=42;}
    }
    static class B extends A {

    }
}

Vous ne pouvez pas accéder au membre privé directement à partir d'un ancêtre, c'est ce que signifie privé. Maintenant, pourquoi ça marche quand vous lancez? Et cela signifie-t-il que la documentation est incorrecte?

J'ai mentionné à un collègue que la documentation java peut être erronée et il souligne que vous définissez réellement la valeur de foo à l'intérieur de la classe A. Donc tout est correct. Vous ne pouvez pas (parce que c'est privé) Accès foo à partir d'un descendant, vous devez donc lancer. Et vous ne pouvez pas le faire en dehors du corps de A.

Je crois que c'est la bonne réponse.

-1
répondu Richard 2015-02-12 13:18:22