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

时间:2015-03-10 13:23:49

标签: 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);
}

如上所述,我定义了一个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);
}

我尝试声明一个PLUS特定的static变量,但最终会出错:

  

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

如果PLUS是匿名类,为什么我不能在PLUS内定义静态常量?这两条错误消息听起来相互矛盾,因为第一条错误消息显示PLUS(){...}具有静态上下文,而第二条错误消息显示PLUS(){...}具有非静态< / strong>上下文。我现在更加困惑。

8 个答案:

答案 0 :(得分:31)

这是一个奇怪的案例。

问题似乎是:

  • 在这种情况下,私人会员应该可以访问6.6.1。):

      

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

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

      

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

  • 因此,printMe不是匿名子类的成员,编译器会在超类* Operation15.12.1)中搜索它:

      

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

         

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

  • 这就是奇怪的地方。因为在{em>也包含 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;
}

  

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

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

  

匿名类始终是内部类;永远不会static

  

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

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

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

italics中的所有内容都是静态上下文。从bold中的表达式派生的匿名类被视为内部和非静态(但没有Example的封闭实例。)

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


*除了模糊一点之外,Operation是一个枚举的事实完全无关紧要(8.1.3):

  

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

答案 1 :(得分: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(); 
            }
        };
    }
}

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

上面的代码为我提供了与枚举完全相同的错误:&#34;错误:非静态方法bar()无法从静态上下文引用&#34;。

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

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

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

答案 2 :(得分:6)

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

至于您的原始问题,了解正在发生的事情的最明确方法是尝试使用this.printMe();。然后,错误消息更容易理解,并提供真实的原因printMe();无法正常工作:

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

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

解决此问题的两种方法是使printMe受保护,或编写

((Operation) this).printMe();

答案 3 :(得分:5)

当您使用PLUS派生一个新的匿名类时,

printMe不应该是private

protected void printMe(double val) {

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

答案 4 :(得分:4)

PLUS()的类型是什么? 这基本上是enum Operation的类型。

如果你想将它与java class进行比较,它本身就是同一类的object

现在enum Operation具有抽象方法apply,这意味着任何此类型(即操作)都应该实现此方法。到现在为止还挺好。

现在是你得到错误的棘手部分。

如您所见,PLUS()基本上是Operation的一种类型。 Operation有一个private方法printMe(),只有enum Operation本身才能看到它不包含任何其他枚举,包括子枚举(就像java中的子类和超类一样) 。此方法也不是static,这意味着除非你实例化类,否则你不能调用它。所以你最终得到了两个解决问题的方法,

  1. printMe() method static作为编译器建议
  2. 使方法protected如此sub-enum inherits这种方法。

答案 5 :(得分:3)

在这种情况下,使静态工作的原因是自动合成访问器功能。如果它是私有静态,您仍会收到以下编译器警告。

  

从“操作”类型访问封闭方法printMe(double)   通过合成访问器方法模拟

在这种情况下唯一不起作用的是私有非静态。所有其他安全工作,例如私有静态,受保护的非静态等等。正如其他人所提到的,PLUS是Operation的一个实现,因此私有技术上不起作用,Java自动帮助使用自动合成访问器功能为您修复它。

答案 6 :(得分:2)

使printMe方法静态解决编译错误:

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

答案 7 :(得分:1)

我在这方面做了很多努力,但我认为理解它的最好方法是看一个不涉及enum的类似案例:

public class Outer {

    protected void protectedMethod() {
    }

    private void privateMethod() {
    }

    public class Inner {
        public void method1() {
            protectedMethod();   // legal
            privateMethod();     // legal
        }
    }

    public static class Nested {
        public void method2() {
            protectedMethod();  // illegal
            privateMethod();    // illegal
        }
    }

    public static class Nested2 extends Outer {
        public void method3() { 
            protectedMethod();  // legal
            privateMethod();    // illegal
        }
    }    
}

Inner的对象是内部类;每个这样的对象都包含对封闭的Outer类的对象的隐藏引用。这就是致protectedMethodprivateMethod合法的原因。他们被称为包含Outer对象,即hiddenOuterObject.protectedMethod()hiddenOuterObject.privateMethod()

Nested的对象是静态嵌套类;没有与Outer类相关联的对象。这就是为什么对protectedMethodprivateMethod的调用是非法的 - 没有Outer对象供他们使用。错误消息为non-static method <method-name>() cannot be referenced from a static context。 (请注意,privateMethod此时仍然可见。如果method2具有outer类型的其他对象Outer,则可以合法地调用outer.privateMethod()。但是示例代码,没有对象可供使用。)

Nested2的对象同样是一个静态嵌套类,但有一个扭曲,它扩展了Outer。由于Outer的受保护成员将被继承,因此调用protectedMethod()是合法的;它将在类Nested2的对象上运行。但是,私有方法privateMethod()不会被继承。因此,编译器对它的处理方式与对Nested的处理方式相同,后者会产生相同的错误。

enum案例与Nested2案件非常相似。每个带有body的枚举常量都会导致创建一个新的匿名子类Operation,但它实际上是一个静态嵌套类(即使匿名类通常是内部类)。 PLUS对象没有对类Operation的对象的隐藏引用。因此,可以引用继承的公共成员和受保护成员,并对PLUS对象进行操作;但是Operation中对私有成员的引用不会被继承,并且无法访问它们,因为没有可以处理的隐藏对象。错误消息Cannot make a static reference to the non-static method printMe()non-static method cannot be referenced from a static context几乎相同,只是单词的顺序不同。 (我并没有声称所有语言规则都与Nested2案例一样;但在这种情况下,它确实帮助我将它们视为几乎相同的构造。)

这同样适用于对受保护和私有字段的引用