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

Joh*_*ica 24 java private

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

class B extends A { }
Run Code Online (Sandbox Code Playgroud)

这无法编译错误:

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

将强制转换添加到基类使其工作.

void bar(B b) { ((A) b).foo = 42; }
Run Code Online (Sandbox Code Playgroud)

有人能指出我为什么第一个片段是非法的解释?它被禁止的原因是什么?以下是JLS所说的内容:

否则,声明成员或构造函数private,当且仅当它发生在包含成员或构造函数声明的顶级类(第7.6节)的主体内时才允许访问.

我可以说,我的代码符合这一措辞.那么这是Java编译器的错误,还是我对JLS的解释不正确?

(注意:我不是在寻找变通方法,比如制作变量protected.我知道如何解决这个问题.)

Nik*_*lay 16

错误消息"在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 {}
Run Code Online (Sandbox Code Playgroud)

他们试图修复它并导致它

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
======
Run Code Online (Sandbox Code Playgroud)

但是规范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)也是类中的一个函数Ab != this

JLS 6.6.1.确定可访问性

否则,如果成员或构造函数被声明为private,则当且仅当它发生在包含成员或构造函数声明的顶级类(第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);
  }
}
Run Code Online (Sandbox Code Playgroud)

它将编译,因为Java推断多态调用的类型参数.

JLS 15.12.2.7.根据实际参数推断类型参数:

超类型约束T:> X意味着解决方案是X的超类型之一.在T上给出了几个这样的约束,我们可以交叉每个约束隐含的超类型集合,因为类型参数必须是所有约束的成员.他们.然后我们可以选择交叉点中最具体的类型

  • "所以b.foo是非法的,因为B类没有名为foo的字段." - 请注意,编译器错误消息是"foo"不可访问,而不是它不存在."...当且仅当它出现在包含成员或构造函数声明的顶级类(第7.6节)的主体内时才允许访问." - 正如我所看到的,我的代码满足了这个条件. (2认同)
  • 编译器行为符合规范,但错误消息不符合规范.很有意思... (2认同)
  • 令我困惑的是,"B"的实例有一个字段`foo`但是类型`B`没有,而我引用的JLS部分只适用于该类型的成员. (2认同)

Tar*_*rik 13

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

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

使用this关键字的最常见原因是因为字段被方法或构造函数参数遮蔽.

当你使用((A) b)时,你正在构建A引用类型,编译器会看到它就像你使用引用变量类型,换句话说,类似的东西A a,并且a.foo是完全合法的.

可见性和对超类私有实例变量的访问的说明性摘要: 这里

  • @JohnKugelman我想也许错误信息会误导你.实际错误应该是"foo无法解析或不是字段",因为对于对象中不存在的任何字段.试图在'B`中查找`foo`是没有意义的.只有编译器试图友好并告诉你*为什么*在`B`中没有`foo`.一旦你施放它,你有一个类型为'A`的值,就`A`而言,该值肯定有一个`foo`变量. (3认同)
  • 这个答案应该是公认的答案.如果您在类之外引用它,则无法访问私有变量."为什么这不编译?" 您无法访问类类型之外的私有变量."但我是从子类中调用它,并使用私有变量在类中包含代码." 您仍然无法访问它,因为您的子类不是超类的强引用类型.它是继承的.这就是为什么类型转换有效的原因.http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html (2认同)