枚举常量特定的类体是静态的还是非静态的?

use*_*225 33 java enums static anonymous-inner-class

我有一个枚举类型:

public enum Operation {
    PLUS() {
        @Override
        double apply(double x, double y) {       
            // ERROR: Cannot make a static reference
            // to the non-static method printMe()...
            printMe(x);
            return x + y;
        }
    };

    private void printMe(double val) {
        System.out.println("val = " + val);
    }

    abstract double apply(double x, double y);
}
Run Code Online (Sandbox Code Playgroud)

如上所述,我定义了一种enum有价值的类型PLUS.它包含一个不变的特定体.在它的正文中,我试着打电话 printMe(val);,但我得到了编译错误:

无法对非静态方法printMe()进行静态引用.

为什么我会收到此错误?我的意思是我在PLUS体内覆盖了抽象方法.为什么它在static范围内?如何摆脱它?

我知道添加一个static关键字来printMe(){...}解决问题,但我有兴趣知道如果我想保持printMe()非静态是否还有其他方法?


另一个问题,与上面的问题非常类似,但这次错误消息反过来说,即PLUS(){...}具有非静态上下文:

public enum Operation {
    PLUS() {
        // ERROR: the field "name" can not be declared static
        // in a non-static inner type.
        protected static String name = "someone";

        @Override
        double apply(double x, double y) {
            return x + y;
        }
    };

    abstract double apply(double x, double y);
}
Run Code Online (Sandbox Code Playgroud)

我尝试声明一个PLUS特定的static变量,但我最终得到了错误:

字段"name"不能在非静态内部类型中声明为static.

PLUS如果PLUS是匿名类,为什么我不能在内部定义静态常量?这两个错误消息听起来相互矛盾的,作为第一个错误消息指出PLUS(){...}静态的背景下,而第二错误消息指出PLUS(){...}非静态上下文.我现在更加困惑.

Rad*_*def 31

这是一个奇怪的案例.

看来问题是:

  • 在这种情况下,私有成员应该是可访问的(6.6.1.):

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

  • 但是,私有成员不是继承的(8.2):

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

  • 因此,printMe不是匿名子类的成员,并且编译器在超类*Operation(15.12.1)中搜索它:

    如果存在该方法是成员的封闭类型声明,则让T为最内层的类型声明.要搜索的类或接口是T.

    此搜索策略称为"梳理规则".在查找封闭类及其超类层次结构中的方法之前,它有效地在嵌套类的超类层次结构中查找方法.

  • 这就是它变得奇怪的地方.因为printMe在一个也包含 的类中找到PLUS,所以调用该方法的对象被确定为一个封闭的实例Operation,它不存在(15.12.4.1):

    否则,让T为该方法所属的封闭类型声明,并且让n为整数,使得T是类的第n个词法封闭类型声明,其声明立即包含方法调用.目标引用是第n个词法封闭的实例this.

    如果第n个词法封闭的实例this不存在,则是编译时错误.

简而言之,因为printMe只是Operation(而不是继承)的成员,编译器被迫printMe 在不存在的外部实例上调用.

但是,该方法仍然可访问,我们可以通过限定调用来找到它:

@Override
double apply(double x, double y) {
//  now the superclass is searched
//  but the target reference is definitely 'this'
//  vvvvvv
    super.printMe(x);
    return x + y;
}
Run Code Online (Sandbox Code Playgroud)

这两条错误信息彼此相互矛盾[...].

是的,这是语言的一个令人困惑的方面.一方面,匿名类永远不是静态的(15.9.5),另一方面,匿名类表达式可以出现在静态上下文中,因此没有封闭的实例(8.1.3).

匿名类总是内部类; 它永远不会static.

I声明在静态上下文中发生的内部类的实例没有词法封闭的实例.

为了帮助理解这是如何工作的,这是一个格式化的例子:

class Example {
    public static void main(String... args) {
        new Object() {
            int i;
            void m() {}
        };
    }
}

一切italics都是静态背景.从表达式派生的匿名类bold被认为是内部和非静态(但没有封闭的实例Example).

由于匿名类是非静态的,因此它不能声明静态非常量成员,尽管它本身是在静态上下文中声明的.


*除了稍微模糊一下这个问题之外,Operation枚举的事实完全无关紧要(8.9.1):

枚举常量的可选类体隐式定义了一个匿名类声明,该声明扩展了直接封闭的枚举类型.班级机构由匿名班级的通常规则管理[...].


Edw*_*rzo 10

我不认为我有关于错误性质的答案,但也许我可以为讨论做出贡献.

当Java编译器编译你的枚举代码时,它会产生一个合成类,如下所示:

class Operation {

    protected abstract void foo();
    private void bar(){ }

    public static final Operation ONE;

    static {
        ONE = new Operation() {
            @Override
            protected void foo(){
                bar(); 
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过在其中一个枚举类中运行javap来验证枚举代码看起来有点像这样.

上面的代码给出了你在enum上得到的完全相同的错误:"错误:非静态方法bar()不能从静态上下文中引用".

所以这里编译器认为你不能bar()从定义匿名类的静态上下文中调用方法,这是一个实例方法.

它对我来说没有意义,它应该是可访问的或被拒绝访问,但错误似乎不准确.我仍然感到困惑,但这似乎是实际发生的事情.

如果编译器说匿名类没有访问权限会更有意义,foo因为它在父类上是私有的,但是编译器正在触发这个其他错误.


Pau*_*ton 6

更新后的问题很容易回答.永远不允许匿名类使用静态成员.

至于你原来的问题,理解正在发生的事情的最清楚的方法是尝试this.printMe();.然后错误消息更容易理解,并给出真正的原因printMe();不起作用:

'printMe(double)' has private access in 'Operation'
Run Code Online (Sandbox Code Playgroud)

你不能使用的原因printMe是因为它privatethis引用的编译时类型是匿名扩展类Operation,而不是Operation它本身(参见Edwin Dalorzo的回答).您刚刚编写时会收到不同的错误消息,printMe();因为由于某种原因,编译器甚至没有意识到您正在尝试调用实例方法this.如果您尝试printMe根本不调用任何实例(即,它就像是一个静态方法),它会给出错误消息.如果通过编写使其明确,则错误消息不会更改Operation.printMe();.

解决这个问题的两种方法是printMe保护或写入

((Operation) this).printMe();
Run Code Online (Sandbox Code Playgroud)


Joo*_*gen 5

printMe不应该private像使用PLUS派生一个新的匿名类一样.

protected void printMe(double val) {
Run Code Online (Sandbox Code Playgroud)

至于错误的性质,enum/Enum是一个神器; 目前我不能理解:内心阶级可能会访问私人事物......