提问者:小点点

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


我有一个枚举类型类:

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

正如您在上面看到的,我定义了一个enum类型,其值为PLUS。它包含一个特定于常量的主体。在它的主体中,我试图调用printMe(val);,但我得到了编译错误:

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

为什么会出现这个错误?我的意思是我正在覆盖PLUSbody中的抽象方法。为什么它在静态范围内?如何摆脱它?

我知道在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);
}

我尝试声明一个PLUS-特定的静态变量,但最终出现错误:

字段“名称”不能在非静态内部类型中声明为静态。

如果PLUS是一个匿名类,为什么我不能在PLUS中定义静态常量?这两个错误消息听起来相互矛盾,因为第一个错误消息说PLUS(){…}有静态上下文,而第二个错误消息说PLUS(){…}有非静态上下文。我现在更加困惑了。


共3个答案

匿名用户

这是一个奇怪的案例。

问题似乎是:

>

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

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

    但是,私有成员不会被继承(8.2):

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

    因此,printMe不是匿名子类的成员,编译器会在超类*操作(15.12.1)中搜索它:

    如果有一个封闭的类型声明,该方法是其成员,则让T成为最里面的此类类型声明。要搜索的类或接口是T。

    这种搜索策略被称为“梳状规则”。它有效地在嵌套类的超类层次结构中查找方法,然后再在封闭类及其超类层次结构中查找方法。

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

    否则,让T是方法是其成员的封闭类型声明,让n是一个整数,使得T是其声明立即包含方法调用的类的第n个词法封闭类型声明。目标引用是this的第n个词法封闭实例。

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

    所以简而言之,因为printMe只是操作的成员(而不是继承的),编译器被迫在不存在的外部实例上调用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;
    }
    

    这两个错误消息听起来相互矛盾[…]。

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

    匿名类始终是内部类;它永远不是静态

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

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

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

    斜体中的所有内容都是静态上下文。从大胆中的表达式派生的匿名类被认为是内部的和非静态的(但没有示例的封闭实例)。

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

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

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

  • 匿名用户

    我想我没有关于错误性质的答案,但也许我可以为讨论做出一点贡献。

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

    class Operation {
    
        protected abstract void foo();
        private void bar(){ }
    
        public static final Operation ONE;
    
        static {
            ONE = new Operation() {
                @Override
                protected void foo(){
                    bar(); 
                }
            };
        }
    }
    

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

    上面的这段代码给了我与您在枚举上得到的完全相同的错误:“错误:无法从静态上下文引用非静态方法bar()”。

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

    这对我来说毫无意义,它应该要么是可访问的,要么是被拒绝访问的,但错误似乎并不准确。我仍然感到困惑,但这似乎是实际发生的事情。

    如果编译器说匿名类无权访问foo,因为它在其父类上是私有的,但编译器却触发了另一个错误,这将更有意义。

    匿名用户

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

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

    'printMe(double)' has private access in 'Operation'
    

    您不能使用printMe的原因是因为它是私有,并且这个引用的编译时类型是操作的匿名扩展类,而不是操作本身(参见Edwin Dalorzo的回答)。当您只编写printMe();时,您会收到不同的错误消息,因为出于某种原因,编译器甚至没有意识到您正在尝试在this上调用实例方法。如果您尝试在根本没有实例的情况下调用printMe,它会给出有意义的错误消息(即好像它是一个静态方法)。如果您通过编写Operation. printMe();来明确错误消息,则错误消息不会更改。

    解决这个问题的两种方法是使printMe受到保护,或者编写

    ((Operation) this).printMe();