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

时间:2015-02-07 00:20:44

标签: java private

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节)的主体内时才允许访问。 / p>

我能说清楚,我的代码符合这个措辞。那么这是Java编译器的一个错误,还是我对JLS的解释不正确?

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

7 个答案:

答案 0 :(得分: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 {}

他们试图修复它并导致

  

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:new javac假定私有字段由子类继承

当泛型来的时候

  

JDK-6246814:类型变量的私有成员可以错误地访问

     

JDK-7022052:私有方法和泛型上的编译器错误无效


但是,通过JLS,该成员根本不存在于继承类型中。

  

JLS 8.2。班级成员

     

声明为private的类的成员不会被该类的子类继承。

所以b.foo是非法的,因为类B没有名为foo的字段。 这不是限制,它是B中缺席的字段。

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

投靠(A) b是合法的,因为BA的子类。

A有一个名为foo的字段,我们可以访问此私有字段,因为b(B b)是类A中的一个函数,即使b != this归因于< / p>

  

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

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

  

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

     

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

答案 1 :(得分:13)

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

如果您想使用foo而不使用像第二个示例那样的明确演员,则必须使用this.foofoo具有隐式this的{​​{1}}。在指定Javadocs时,this关键字的主要原因是为了防止:

  

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

当您使用((A) b)时,您正在构建引用类型,如果您使用A引用变量类型,编译器将会看到它,换句话说就像A a之类的内容,以及a.foo完全合法。

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

答案 2 :(得分:2)

specification for field access expressions, chapter 15.11说:

  

如果标识符未在类型T中命名可访问的成员字段,   那么字段访问是未定义的,并且发生编译时错误。

从超类的角度来看,看看这些类型,我认为该成员无法访问,因此错误。

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

答案 3 :(得分:1)

在我看来,规范是不一致的。正如约翰所说,规范的主体陈述

  

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

并没有提到子类。所以A类应该正确编译。但是示例6.6-5说明

  

私有类成员或构造函数只能在包含成员或构造函数声明的顶级类(第7.6节)的主体内访问。 它不是由子类继承的。

这第二个语句更弱(不仅仅是if),但是将子类带到表中。根据这个A不应该编译。

答案 4 :(得分:0)

Java很难通过引用类型访问私有变量,而引用类型不应该访问该变量。您应该能够通过撰写((A) b).foo = 42合法地执行此操作。

答案 5 :(得分:-1)

我们无法继承private字段或方法。因此,在您的代码中,即使您要从自己的类B访问,foo类也完全不知道变量A

答案 6 :(得分:-1)

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

试试这个:

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

    }
}

您不能直接从祖先访问私有成员,这是私有的意思。现在为什么你演员时会有效?这是否意味着文档不正确?

我向一位同事提到java文档可能是错误的,他指出你实际上是从A类中设置了foo的值。所以一切都是正确的。你不能(因为它是私人的)从后代访问foo,所以你必须强制转换。你不能在A的身体之外做到这一点。

我相信这是正确答案。