无法通过 subclass 实例从自己的 class 访问私有变量

Can't access private variable from own class via subclass instance

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

在基础上添加一个转换 class 使其工作。

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

谁能给我解释一下为什么第一个片段是非法的?被禁止的原因是什么? JLS 是这样说的:

Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

据我所知,我的代码符合这个措辞。这是 Java 编译器的错误,还是我对 JLS 的解释不正确?

(注意:我不是在寻找变通办法,比如创建变量 protected。我知道如何解决这个问题。)

Java 对于通过不应访问该变量的 引用类型 访问私有变量很挑剔。你应该可以通过写 ((A) b).foo = 42.

来合法地做到这一点

你不能说 b.foo 因为 foo 是私有的,因此不会被继承,因此 B class 看不到 foo 变量并且不知道是否存在名为 foo 的变量 - 除非它标记为受保护(如你所说)或默认(因为它们在同一个包中,我猜)或 public。

如果你想使用 foo 而不是像你的第二个例子那样使用显式转换,你必须使用 this.foo 或只使用 foo ,它有一个隐式的 this。正如 Javadocs 所指定的,this 关键字的主要原因是为了防止:

The most common reason for using the this keyword is because a field is shadowed by a method or constructor parameter.

当您使用 ((A) b) 时,您正在强制转换引用类型,编译器将看到它就像您使用 A 引用变量类型一样,换句话说类似于 A a , 而 a.foo 是完全合法的。

对超级class的私有实例变量的可见性和访问的图示总结:

错误消息“在 A 中有私人访问权限”是一个 java 很长一段时间的错误。

JDK 1.1:

JDK-4096353 : JLS 6.6.1: When subclass references are used to access privates of superclasses

包含完全符合问题一的代码片段

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's error messages are not appropriate for a private field.

======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 private members should not be inherited from superclasses

JDK-4249653 : new javac assumes that private fields are inherited by a subclass

当泛型出现时

JDK-6246814 : Private member of type variable wrongly accesible

JDK-7022052 : Invalid compiler error on private method and generics


但是,对于 JLS,此成员根本不存在于继承类型中。

JLS 8.2. Class Members

Members of a class that are declared private are not inherited by subclasses of that class.

所以 b.foo 是非法的,因为 class B 没有名为 foo 的字段。 没有限制,是B.

中的缺席字段

Java 具有强类型,我们无法访问 B 中不存在的字段,即使它们存在于 superclass A.

转换 (A) b 是合法的,因为 BA 的子class。

A 有一个名为 foo 的字段,我们可以访问这个私有字段,因为 b(B b) 是 class A 中的一个函数,即使 b != this 由于

JLS 6.6.1. Determining Accessibility

Otherwise, if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

另外如果我们写

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 推断多态调用的类型参数。

JLS 15.12.2.7. Inferring Type Arguments Based on Actual Arguments:

A supertype constraint T :> X implies that the solution is one of supertypes of X. Given several such constraints on T, we can intersect the sets of supertypes implied by each of the constraints, since the type parameter must be a member of all of them. We can then choose the most specific type that is in the intersection

specification for field access expressions, chapter 15.11 说:

If the identifier does not name an accessible member field in type T, then the field access is undefined and a compile-time error occurs.

从超级 class 的角度来看,从类型来看,我认为该成员 不可访问 ,因此出现错误。

我认为您介绍的案例更接近于将成员作为字段访问,如示例 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.";
    }
}

只是回答你的问题:

Kindly explain what sort of bad code the restriction is protecting against.

请考虑以下代码段。

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

我们无法继承 private 字段或方法。所以在你的代码中 Class B 完全不知道变量 foo 即使你从它自己的 class A.

访问

这不是默认访问修饰符的用途吗?

试试这个:

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

    }
}

您不能直接从祖先访问私有成员,这就是私有的意思。现在为什么它在你投射时起作用?这是否意味着文档不正确?

我向一位同事提到 java 文档可能有误,他指出您实际上是从 class A 内部设置 foo 的值。所以一切都是正确的。您不能(因为它是私有的)从后代访问 foo,所以您必须投射。你不能在 A.

的 body 之外这样做

我相信这是正确答案。

在我看来规范不一致。正如 John 所说,规范的正文指出

Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

并且没有提到 subclasses。所以 class A 应该可以正确编译。但是示例 6.6-5 指出

A private class member or constructor is accessible only within the body of the top level class (§7.6) that encloses the declaration of the member or constructor. It is not inherited by subclasses.

这第二个陈述既较弱(不仅是如果),但给 table 带来了子classes。按照这个A应该编译不了。