Подтвердить что ты не робот

Не удается получить доступ к частной переменной из собственного класса через экземпляр подкласса.

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

class B extends A { }

Это не скомпилируется с ошибкой:

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

Добавление приведения в базовый класс заставляет его работать.

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

Может кто-нибудь указать мне на объяснение, почему первый фрагмент является незаконным? Какая причина, по которой это запрещено? Здесь JLS говорит:

В противном случае член или конструктор объявляется private, и доступ разрешен тогда и только тогда, когда он встречается внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора.

Насколько я могу судить, мой код соответствует этой формулировке. Так это ошибка с компилятором Java, или моя интерпретация JLS неверна?

(Примечание: я не ищу обходные методы, например, сделать переменную protected. Я знаю, как обойти это.)

4b9b3361

Ответ 1

Сообщение об ошибке "имеет частный доступ в A" является ошибкой java в течение очень долгого времени.

JDK 1.1:

JDK-4096353: JLS 6.6.1: Когда ссылки подкласса используются для доступа к рядовым суперклассам

содержит фрагмент кода, который точно соответствует одному вопросу

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 {}

Они пытались его исправить, и это приводит к

JDK-4122297: сообщения об ошибках javac не подходят для частного поля.

======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
======

Но по спецификации java не наследуется в C, и эта программа должна компилироваться.

Он исправлен в 1.2, но снова появляется в 1.3

JDK-4240480: name00705.html: JLS6.3 личные члены не должны наследоваться от суперклассов

JDK-4249653: новый javac предполагает, что частные поля наследуются подклассом

И когда генерические приходят

JDK-6246814: Частный член переменной типа ошибочно доступен

JDK-7022052: Недопустимая ошибка компилятора при использовании частного метода и дженериков


Однако в JLS этот член просто не существует в унаследованном типе.

JLS 8.2. Члены класса

Члены класса, объявленные как private, не наследуются подклассами этого класса.

Итак, b.foo является незаконным, поскольку класс B не имеет поля с именем foo. Это не ограничение, это поле отсутствует в B.

Java имеет сильную типизацию, и мы не можем получить доступ к полям, которые не существуют в B, даже если они существуют в суперклассе A.

Cast (A) b является законным, потому что B является подклассом A.

A имеет поле с именем foo, и мы можем получить доступ к этому приватному полю, потому что b(B b) - это функция в классе A, даже если b != this из-за

JLS 6.6.1. Определение доступности

В противном случае, если член или конструктор объявлен приватным, доступ разрешен тогда и только тогда, когда он встречается внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора.

Также, если мы пишем

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);
  }
}

Он будет скомпилирован, потому что Java infer аргументы типа для полиморфных вызовов.

JLS 15.12.2.7. Вывод аргументов типа на основе фактических аргументов:

Ограничение супертипа T: > X означает, что решение является одним из супертипов X. Учитывая несколько таких ограничений на T, мы можем пересечь множества супертипов, подразумеваемых каждым из ограничений, так как параметр типа должен быть членом из всех них. Затем мы можем выбрать наиболее специфический тип, который находится в пересечении

Ответ 2

Вы не можете сказать b.foo, потому что foo является закрытым и, следовательно, не будет наследоваться, в результате класс B не сможет увидеть переменную foo и не знает, будет ли переменная с именем foo даже существует - если его отмеченные защищенные (как вы сказали) или по умолчанию (поскольку они находятся в одном пакете, я думаю) или публичные.

Если вы хотите использовать foo без использования явного приведения, как ваш второй пример, вы должны использовать this.foo или просто foo, который имеет неявный this. Как указано Javadocs, ключевая причина ключевого слова this заключается в том, чтобы предотвратить следующее:

Наиболее распространенная причина использования этого ключевого слова заключается в том, что поле затенен параметром метода или конструктора.

Когда вы использовали ((A) b), вы выбрали ссылочный тип, и компилятор увидит его, как если бы вы использовали тип ссылочной переменной A, другими словами, что-то вроде A a и a.foo является полностью законным.

Иллюстрированное резюме видимости и доступа к переменным частного экземпляра суперкласса: here

Ответ 3

спецификация для выражений доступа к полю, глава 15.11 говорит:

Если идентификатор не называет поле доступного члена в типе T, то доступ к полю будет undefined и произойдет ошибка времени компиляции.

С точки зрения суперкласса, глядя на типы, я бы утверждал, что член недоступен, следовательно, ошибка.

Я думаю, что случай, который вы представляете, ближе к доступу к члену как к полю, которое показано в примере 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.";
    }
}

Просто чтобы ответить на ваш вопрос:

Просьба объяснить, какой тип кода защищает против.

Пожалуйста, рассмотрите следующий фрагмент.

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;
}

Ответ 4

Мне кажется, что спецификация непоследовательна. Как утверждает Джон, тело специфицированных состояний

В противном случае член или конструктор объявляется закрытым, а доступ разрешен только тогда, когда он встречается внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора.

и нет упоминаний о подклассах. Поэтому класс A должен правильно компилироваться. Однако пример 6.6-5 состояния

Частный член класса или конструктор доступен только внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора. Он не наследуется подклассами.

Этот второй оператор является более слабым (не только если), но приносит подклассы в таблицу. В соответствии с этим A не следует компилировать.

Ответ 5

Java отлично подходит для доступа к частным переменным через ссылочный тип, который не должен иметь доступ к этой переменной. Вы должны иметь возможность сделать это юридически, написав ((A) b).foo = 42.

Ответ 6

Мы не можем наследовать поля или методы private. Таким образом, в вашем коде Class B полностью не осознает переменную foo, даже если вы получаете доступ к ее собственному классу A.

Ответ 7

Разве это не то, что используется для модификатора доступа по умолчанию?

Попробуйте следующее:

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

    }
}

Вы не можете получить доступ к частному члену непосредственно от предка, это то, что личное. Теперь, почему это работает, когда вы бросаете? И означает ли это, что документация неверна?

Я сказал коллеге, что java-документация может быть ошибочной, и он указывает, что вы фактически устанавливаете значение foo изнутри класса A. Итак, все правильно. Вы не можете (потому что это частный) доступ foo от потомка, поэтому вы должны бросить. И вы не можете сделать это за пределами тела A.

Я считаю, что это правильный ответ.