Is an enum constant-specific class body static or non-static?

和自甴很熟 提交于 2019-11-26 15:50:02

问题


I have a enum type 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);
}

As you see above, I have defined one enum type which has value PLUS. It contains a constant-specific body. In its body, I tried to call printMe(val);, but I got the compilation error:

Cannot make a static reference to the non-static method printMe().

Why do I get this error? I mean I am overriding an abstract method in PLUS body. Why is it in static scope? How to get rid of it?

I know adding a static keyword on printMe(){...} solves the problem, but I am interested to know if there is another way if I want to keep printMe() non-static?


Another issue, quite similar as above one, but this time the error message sounds the other way around, i.e. PLUS(){...} has non-static context:

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

I try to declare a PLUS-specific static variable, but I end up with error:

the field "name" can not be declared static in a non-static inner type.

Why can I not define static constant inside PLUS if PLUS is an anonymous class? The two error messages sound contradictory to each other, as the 1st error message says PLUS(){...} has static context while the 2nd error message says PLUS(){...} has non-static context. I am even more confused now.


回答1:


Well this is a strange case.

It appears that the problem is:

  • In this case, the private member should be accessible (6.6.1.):

    Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level class that encloses the declaration of the member or constructor.

  • However, private members are not inherited (8.2):

    Members of a class that are declared private are not inherited by subclasses of that class.

  • Therefore, printMe is not a member of the anonymous subclass and the compiler searches for it within the superclass* Operation (15.12.1):

    If there is an enclosing type declaration of which that method is a member, let T be the innermost such type declaration. The class or interface to search is T.

    This search policy is called the "comb rule". It effectively looks for methods in a nested class's superclass hierarchy before looking for methods in an enclosing class and its superclass hierarchy.

  • And here is where it gets strange. Because printMe is found in a class that also encloses PLUS, the object that the method is called on is instead determined to be an enclosing instance of Operation, which doesn't exist (15.12.4.1):

    Otherwise, let T be the enclosing type declaration of which the method is a member, and let n be an integer such that T is the n'th lexically enclosing type declaration of the class whose declaration immediately contains the method invocation. The target reference is the n'th lexically enclosing instance of this.

    It is a compile-time error if the n'th lexically enclosing instance of this does not exist.

So in short, because printMe is only a member of Operation (and not inherited), the compiler is compelled to invoke printMe on a non-existent outer instance.

However, the method is still accessible and we can find it by qualifying the invocation:

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

The two error messages sound contradictory to each other [...].

Yes, this is a confusing aspect of the language. On the one hand, an anonymous class is never static (15.9.5), on the other hand, an anonymous class expression can appear in a static context and therefore has no enclosing instance (8.1.3).

An anonymous class is always an inner class; it is never static.

An instance of an inner class I whose declaration occurs in a static context has no lexically enclosing instances.

To help understand how this works, here is a formatted example:

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

Everything in italics is the static context. The anonymous class derived from the expression in bold is considered inner and non-static (but has no enclosing instance of Example).

Since the anonymous class is non-static it cannot declare static non-constant members, despite that it is itself declared within a static context.


* Besides obscuring the matter a little, the fact that Operation is an enum is completely irrelevant (8.9.1):

The optional class body of an enum constant implicitly defines an anonymous class declaration that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes [...].




回答2:


I don't think I have the answer about the nature of the error, but perhaps I can contribute a bit to the discussion.

When the Java compiler compiles your enum code, it produces a synthetic class that is somewhat as follows:

class Operation {

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

    public static final Operation ONE;

    static {
        ONE = new Operation() {
            @Override
            protected void foo(){
                bar(); 
            }
        };
    }
}

You can verify the enum code looks somewhat like this by running javap in one of your enum classes.

This code above gives me the exact same error you're getting on your enum: "error: non-static method bar() cannot be referenced from a static context".

So here the compiler thinks you cannot invoke the bar() method, which is an instance method, from the static context in which the anonymous class is being defined.

It makes no sense to me, it should either be accesible or be denied access, but the error does not seem accurate. I am still puzzled, but this seems to be what is actually happening.

It would make more sense if the compilers said that that the anonymous class did not have access to foo since it is private on its parent class, but the compiler is firing this other error instead.




回答3:


The question following your update is easy to answer. Anonymous classes are never allowed static members.

As for your original question, the clearest way to understand what's going on is to try this.printMe(); instead. Then the error message is much easier to understand, and gives the real reason printMe(); doesn't work:

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

The reason you can't use printMe is because it is private and the compile-time type of the this reference is an anonymous extension class of Operation, not Operation itself (see Edwin Dalorzo's answer). You get a different error message when you just write printMe(); because for some reason the compiler doesn't even realise you're trying to call an instance method on this. It gives the error message that would make sense if you were trying to call printMe on no instance at all (i.e. as if it were a static method). The error message doesn't change if you make that explicit by writing Operation.printMe();.

Two ways to get around this are to make printMe protected, or to write

((Operation) this).printMe();



回答4:


printMe should not be private as you derive a new anonymous class with PLUS.

protected void printMe(double val) {

As to the nature of the error, enum/Enum, is a bit of an artifact; it eludes me at the moment: an inner class might access private things...




回答5:


what is the type of PLUS()?
well this is basically type of enum Operation.

If you want to compare it to java class, its basically an object of the same class within itself.

now enum Operation has abstract method apply that means any of this type (namely operation) should implement this method . so far so good.

now the tricky part where you get the error.

as you see, PLUS() is basically a type of Operation. Operation has a private method printMe() meaning only enum Operation itself can see it not any other enum including the sub-enums(just like the sub class and super class in java). also this method is not static either meaning you cannot call it unless u instantiate the class. so you are ended up with two options to solve the problem,

  1. make the printMe() method static as compiler suggest
  2. make the method protected so any sub-enum inherits this methods .



回答6:


In this situation, the reason making it static works is the automatic synthetic accessor functionality. You will still get the following compiler warning if it is private static.

Access to enclosing method printMe(double) from the type Operation is emulated by a synthetic accessor method

The only thing that doesn't work in this case is private non-static. All other security works, e.g. private static, protected non-static, etc. As someone else mentioned PLUS is an implementation of Operation, so private technically doesn't work, Java automatically does the favor of fixing it for you with the automatic synthetic accessor functionality.




回答7:


Making the printMe method static solves the compile error:

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



回答8:


I struggled with this one quite a bit, but I think the best way to understand it is to look at an analogous case that doesn't involve 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
        }
    }    
}

An object of class Inner is an inner class; each such object contains a hidden reference to an object of the enclosing Outer class. That's why the calls to protectedMethod and privateMethod are legal. They're called on the containing Outer object, i.e. hiddenOuterObject.protectedMethod() and hiddenOuterObject.privateMethod().

An object of class Nested is a static nested class; there's no object of an Outer class associated with one. That's why the calls to protectedMethod and privateMethod are illegal--there's no Outer object for them to work on. The error message is non-static method <method-name>() cannot be referenced from a static context. (Note that privateMethod is still visible at this point. If method2 had a different object outer of type Outer, it could call outer.privateMethod() legally. But in the example code, there is no object for it to work on.)

An object of class Nested2 is similarly a static nested class, but with a twist that it extends Outer. Since protected members of Outer will be inherited, that makes it legal to call protectedMethod(); it will be operating on the object of class Nested2. The private method privateMethod() isn't inherited, though. So the compiler treats it the same as it does for Nested, which produces the same error.

The enum case is very similar to the Nested2 case. Each enum constant with a body causes a new anonymous subclass of Operation to be created, but it's in effect a static nested class (even though anonymous classes are usually inner classes). The PLUS object does not have a hidden reference to an object of class Operation. Thus, public and protected members, which are inherited, can be referenced and will operate on the PLUS object; but references to private members in Operation are not inherited, and they can't be accessed because there's no hidden object to work on. The error message, Cannot make a static reference to the non-static method printMe(), is pretty much the same as non-static method cannot be referenced from a static context, just with the words in a different order. (I'm not claiming that all the language rules are just like the Nested2 case; but in this case, it's definitely helped me to see them as almost the same sort of construct.)

The same applies to references to protected and private fields.



来源:https://stackoverflow.com/questions/28964926/is-an-enum-constant-specific-class-body-static-or-non-static

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!