[英]Private instance member access from anonymous static instance
请考虑以下代码:
enum E {
A { public int get() { return i; } },
B { public int get() { return this.i; } },
C { public int get() { return super.i; } },
D { public int get() { return D.i; } };
private int i = 0;
E() { this.i = 1; }
public abstract int get();
}
我在前2个枚举常量声明(A和B)上得到编译时错误,但最后2个编译正常(C&D)。 错误是:
A行的错误1:非静态变量我无法从静态上下文中引用
B行的错误2:我在E中有私人访问权限
由于get
是一个实例方法,我不明白为什么我不能访问实例变量i
我想要的方式。
注意:从i
的声明中删除private
关键字也会使代码可编译,我也不明白。
使用Oracle JDK 7u9。
编辑
正如评论中所指出的,这不是枚举特有的,下面的代码会产生相同的行为:
class E {
static E a = new E() { public int get() { return i; } };
static E b = new E() { public int get() { return this.i; } };
static E c = new E() { public int get() { return super.i; } };
static E d = new E() { public int get() { return d.i; } };
private int i = 0;
}
观察到的行为是由Java语言规范强制执行的,特别是对封闭类型字段的隐式访问,以及不继承私有成员的规则。
不合格的现场访问
A { public int get() { return i; } }
规范要求 :
枚举常量的可选类体隐式定义了一个匿名类声明(第15.9.5节),该声明扩展了直接封闭的枚举类型。 班级团体由匿名班级的通常规则管理; 特别是它不能包含任何构造函数。
这使得表达i
有些模棱两可:我们指的是类实例的字段或内部实例? 唉,内部实例不继承该字段 :
声明为private的类的成员不会被该类的子类继承。
因此,编译总结我们的意思来访问外围实例的领域-但在静态块是, 没有外围实例,因此错误。
现场访问通过this
B { public int get() { return this.i; } },
规范要求 :
当用作主表达式时,关键字this表示一个值,该值是对调用实例方法的对象(第15.12节)的引用,或者是对正在构造的对象的引用。
因此,很明显我们想要内部阶级的领域,而不是外部领域。
编译器拒绝字段访问表达式this.i
是 :
声明为private的类的成员不会被该类的子类继承。
也就是说,私有字段只能通过声明字段的类型的引用来访问,而不能通过其子类型来访问。 确实,
B { public int get() { return ((E)this).i; } },
编译得很好。
通过超级访问
像这样,super 指的是调用方法的对象(或正在构造的对象)。 因此,很清楚我们的意思是内在的实例。
另外,super是E
类型,因此声明可见。
通过其他领域访问
D { public int get() { return D.i; } };
这里, D
是对E
声明的静态字段D
的非限定访问。 因为它是一个静态字段,所以使用哪个实例的问题没有实际意义,并且访问有效。
然而它非常脆弱,因为只有在枚举对象完全构造时才分配字段。 如果有人在构造期间调用get(),则会抛出NullPointerException
。
建议
正如我们所看到的,访问其他类型的私有字段受到一些复杂的限制。 由于很少需要,开发人员可能不知道这些微妙之处。
虽然protected
字段会削弱访问控制(即允许包中的其他类访问字段),但它会避免这些问题。
看看这段代码:
public class E
{
final int i;
private final int j;
final E a;
E() { i = j = 0; a = null; }
E(int p_i) {
this.i = this.j = p_i;
a = new E() {
int getI() { return i; }
int getJ() { return j; }
};
}
int getI() { throw new UnsupportedOperationException(); }
int getJ() { throw new UnsupportedOperationException(); }
public static void main(String[] args) {
final E ea = new E(1).a;
System.out.println(ea.getI());
System.out.println(ea.getJ());
}
}
这打印
0
1
并且i
和j
之间的唯一区别是访问级别!
这是令人惊讶的,但这是正确的行为。
更新
看起来确实是因为它是在静态块中定义的。 看看以下内容:
private E works = new E("A", 0) {
public int get() {
return i; // Compiles
}
};
static {
A = new E("A", 0) {
public int get() {
return i; // Doesn't Compile
}
};
}
原版的
我编译了枚举,然后使用Jad对其进行反编译,以查看代码的外观:
static abstract class E extends Enum
{
public static E[] values()
{
return (E[])$VALUES.clone();
}
public static E valueOf(String s)
{
return (E)Enum.valueOf(Foo$E, s);
}
public abstract int get();
public static final E A;
private int i;
private static final E $VALUES[];
static
{
A = new E("A", 0) {
public int get()
{
return A.i;
}
}
;
$VALUES = (new E[] {
A
});
}
private E(String s, int j)
{
super(s, j);
i = 0;
i = 1;
}
}
这使我更清楚A
是在 E
类型的静态init块中定义的匿名内部类 。 在匿名内部类中查找私有成员可见性,我在此答案中找到以下内容( 为什么在匿名类中只能访问最终变量? ):
当您创建匿名内部类的实例时,该类中使用的任何变量都会通过自动生成的构造函数复制它们的值。 这避免了编译器必须自动生成各种额外类型以保持“局部变量”的逻辑状态,例如C#编译器
从此我认为Ai
指的是A中的这个复制变量,而不是i
在E中声明。在E中获取i
的唯一方法是它是静态还是非私有。
private
方法可以在嵌套类中访问,只要它们位于同一个类文件中。
出于这个原因,即使A是E的匿名子类,第一个示例仍然有效。很好奇第二个示例为什么不编译,但我怀疑它是错误的错误消息,因为你可以做
A { public int get() { return super.i; } };
编译但是
A { public int get() { return i; } };
给
error: non-static variable i cannot be referenced from a static context
如果这是一个静态的上下文,那么super.i
就没有任何意义,这显然是不正确的。
正如Marko所说
A { public int get() { return this.i; } };
生成错误消息
error: i has private access in E
这可能更合适。 即你可以明确地访问这个字段,但不能隐式地访问。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.