私有实例成员从匿名静态实例访问

ass*_*ias 17 java enums static private

请考虑以下代码:

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

我在前2个枚举常量声明(A和B)上得到编译时错误,但最后2个编译正常(C&D).错误是:

A行的错误1:非静态变量i无法从静态上下文引用
B行上的错误2:我在E中有私有访问权限

由于get是一个实例方法,我不明白为什么我不能i以我想要的方式访问实例变量.

注意:private从声明中删除关键字i也会使代码可编辑,我也不明白.

使用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;
}
Run Code Online (Sandbox Code Playgroud)

mer*_*ike 6

观察到的行为是由Java语言规范强制执行的,特别是对封闭类型字段的隐式访问,以及不继承私有成员的规则.

不合格的现场访问

A { public int get() { return i; } }
Run Code Online (Sandbox Code Playgroud)

规范要求:

枚举常量的可选类体隐式定义了一个匿名类声明(第15.9.5节),该声明扩展了直接封闭的枚举类型.班级团体由匿名班级的通常规则管理; 特别是它不能包含任何构造函数.

这使得表达式i有些含糊不清:我们是指封闭实例的字段还是内部实例?唉,内部实例不继承该字段:

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

因此,编译总结我们的意思来访问外围实例的领域-但在静态块之中,还有就是没有外围实例,因此错误.

现场访问 this

B { public int get() { return this.i; } },
Run Code Online (Sandbox Code Playgroud)

规范要求:

当用作主表达式时,关键字this表示一个值,该值是对调用实例方法的对象(第15.12节)的引用,或者是对正在构造的对象的引用.

因此,很明显我们想要内部阶级的领域,而不是外部领域.

编译器拒绝字段访问表达式的原因this.i :

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

也就是说,私有字段只能通过声明字段的类型的引用来访问,而不能通过其子类型来访问.确实,

B { public int get() { return ((E)this).i; } },
Run Code Online (Sandbox Code Playgroud)

编译得很好.

通过超级访问

像这样,super 的是调用方法的对象(或正在构造的对象).因此,很清楚我们的意思是内在的实例.

另外,super是类型的E,因此声明可见.

通过其他领域访问

D { public int get() { return D.i; } };
Run Code Online (Sandbox Code Playgroud)

这里D是对在其中D声明的静态字段的非限定访问E.因为它是一个静态字段,所以使用哪个实例的问题没有实际意义,并且访问有效.

然而它非常脆弱,因为只有在枚举对象完全构造时才分配字段.如果有人在施工期间调用get(),NullPointerException则会抛出.

建议

正如我们所看到的,访问其他类型的私有字段受到一些复杂的限制.由于很少需要,开发人员可能不知道这些微妙之处.

虽然使该字段protected会削弱访问控制(即允许包中的其他类访问该字段),但它将避免这些问题.