Java: Definition of methods and variables inside e

2019-02-03 15:24发布

问题:

I was doing some experiments and accidently wrote a code, which is very weird and I don't get it all. I was even surprised that I can compile it. It looks like this:

enum Foo {
    VALUE_1 {
        public int myVariable = 1;
    },
    VALUE_2 {
        public void myMethod() {
            //
        }
    },
    VALUE_3;
}

As expected, it's not possible to access such an element in the following way:

Foo.VALUE_2.myMethod();

The reason is, that compiler will look for that method inside the enumeration itself.

I presumed that it's not possible to access these methods and variables from outside the enumeration. For this reason, I tried to create a parametric constructor and call it with some internal variable:

enum Foo {
    VALUE(internalVariable) {
        int internalVariable = 1;
    };

    private Foo(int param) {
        //
    }
}

It wasn't possible to compile such a construction. Now I was thinking what's the point of defining something inside the constant if there is no way to access it.

I was trying to create the same-named methods in the constant as well in the enumeration itself to check out if it collides in some way. It didn't!

enum Foo {
    VALUE_1 {
        int myVariable = 1;

        public int myMethod() {
            return myVariable;
        }
    },
    VALUE_2 {
        //
    };

    public int myMethod() {
        return 0;
    }
}

And here comes the funny moment! I tried to proceed call of myMethod() inside the enumeration and actually figured out how this Java magic works. Methods, which are defined inside the constant, overrides methods defined inside the enumeration.

Foo.VALUE_1.myMethod(); // Returns 1
Foo.VALUE_2.myMethod(); // Returns 0

However, we can't override variable, right? So I was curious, how it works with variables only.

enum Foo {
    VALUE_1 {
        public int myVariable = 1;
    },
    VALUE_2 {
        //
    };

    public int myVariable = 0;
}

....

System.out.println(Foo.VALUE_1.myVariable); // Returns 0
System.out.println(Foo.VALUE_2.myVariable); // Returns 0

Now I'm finally getting to my questions:

  1. Why I don't get any error if I create public method inside the constant and left enumeration empty without this method? In that case, the method I just defined can't be called at all. Or am I wrong?

    Update: I know that enumeration can implement interface. However, if I haven't specifically said that, whole code is pointless.

    Someone pointed out that even if method can't be accessed from the language in the normal way, it's still possible to use reflection. Well... Why don't we design an inaccessible keyword?

    inaccessible void magicalMethod() {
         //
    }
    

    Such a method will be compiled into the *.class file. When you want to use it, you've to load bytecode by yourself and interpret it.

    I just can't understand, why it's possible to define unreachable method. The only reason I can think is that programmer is working and doesn't have definition of interface yet. So he's just preparing code of single methods and will add "implements" keyword later. Beside this is illogical, it would still require to have such a method in all constants.

    I think this should end up with error, not just warning about unused method. You may forget to add "implement" clause or to define method in the enumeration (which would be overridden) and will realize that just after the first use. Java is very strict language, so I'd expect this behavior.

  2. Why I don't get any error if I create public variable (or field, to be more precise) inside the constant? It can't be accessed in the any case (from the outside). Therefore, modifier "public" doesn't make any sense here.

    Update: It's more less the same thing as in the previous point, except the visibility modifier is completely useless here. It really doesn't matter if it's public, protected or private, because you won't be able to access that anyway. I think this is a bug.

  3. Why it's possible to define a class (without visibility modifiers), but not interface? Yeah, you wouldn't probably want to write so brutal enumeration that you would need to define classes inside the constant and even to use inheritance there. But if it's possible to define classes and abstract classes, it seems little weird.

    Update: This is definitely not something you'd need on regular basis, but I understand that it might be useful. But why it's limited to classes only and interfaces can't be defined as well?

    enum Foo {
        VALUE {
            class MyClass {
                // OK
            }
    
            abstract class MyAbstractClass {
                // OK
            }
    
            interface MyInterface {
                // FAIL. It won't compile.
            }
        }
    }
    
  4. Did you use such a functionality somewhere? I can imagine it might be useful, but it's little confusing. Also, when I was searching for some resources about that, I didn't find anything.

    Update: I'd like to see some practical example wth overridden methods in an enum constant class body. Have you seen it in some open-source project?

Environment:

$ java -version
java version "1.7.0_21"
OpenJDK Runtime Environment (IcedTea 2.3.9) (7u21-2.3.9-0ubuntu0.12.10.1)
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

Thanks for your time and for your answers!

回答1:

Why I don't get any error if I create public method inside the constant and left enumeration empty without this method? In that case, the method I just defined can't be called at all. Or am I wrong?

In fact the compiler should be able to see that the method isn't visible outside the enum constant's class body and warn you if it isn't used - I know for sure that Eclipse does this. As dasblinkenlight points out, such a public method may in fact be an override of a method declared by an interface that the enum implements.

I just can't understand, why it's possible to define unreachable method. The only reason I can think is that programmer is working and doesn't have definition of interface yet. So he's just preparing code of single methods and will add "implements" keyword later. Beside this is illogical, it would still require to have such a method in all constants.

As I already noted, this doesn't specifically apply to enum constant classes. There are many scopes - private nested classes, local classes, anonymous classes - where it's pointless for a member to be public.

The problem with this question is that only the language designers could truly answer it. I can only give my opinion, which is: why should it be an error? The language spec doesn't come for free - everything in the JLS must be painstakingly defined, and then implemented and tested. The real question is, what benefit is there to making it an error? The mere fact is that while an unused member might indicate a bug (hence the warning), it isn't hurting anything.

Why I don't get any error if I create public variable (or field, to be more precise) inside the constant? It can't be accessed in the any case (from the outside). Therefore, modifier "public" doesn't make any sense here.

Same as above - the compiler or at least some IDEs will warn you if a variable isn't used. This is the same as if you declared a public variable in a private nested class and then didn't reference it anywhere. In any case, it's not the priority of the JLS to forbid such situations, notwithstanding that the eye of reflection sees all.

It's more less the same thing as in the previous point, except the visibility modifier is completely useless here. It really doesn't matter if it's public, protected or private, because you won't be able to access that anyway. I think this is a bug.

Here, you're forgetting that members might still be used within the enum constant class body - think of a helper method for example. It's just that in this case access modifiers simply don't matter and can be left off.

Why it's possible to define a class (without visibility modifiers), but not interface? Yeah, you wouldn't probably want to write so brutal enumeration that you would need to define classes inside the constant and even to use inheritance there. But if it's possible to define classes and abstract classes, it seems little weird.

This is a good question, and it took me a while to understand what you mean. To clarify, you're saying that in this situation only the class is allowed:

VALUE_1 {
    class Bar { }
    interface Baz { }
},

To shed light on this, try making the class static:

VALUE_1 {
    static class Bar { }
    interface Baz { }
},

Now neither are allowed. Why? Nothing static can be declared in an enum constant body, because the body is in the context of the instance of that constant. This is similar to being in the scope of an inner (non-static nested) class:

class Outer {

    class Inner {
        // nothing static allowed here either!
    }
}

Static variables, methods, classes, and interfaces (which are implicitly static when nested) are all forbidden in such a scope.

Did you use such a functionality somewhere? I can imagine it might be useful, but it's little confusing. Also, when I was searching for some resources about that, I didn't find anything.

It's unclear what functionality specifically your referring to here. Please update the question to specify what exactly you're looking for - overridden methods in an enum constant class body? Fields? Helper methods? Helper classes? Please clarify.



回答2:

OK, I've actually used this functionality! I'm writing simple game and want to provide two sound packs. Because the game is very simple and won't be probably extended in the future, I didn't want to create some complex mechanism for achieving such a thing.

public enum SoundPack {
    CLASSICAL {
        @Override
        public String getSoundPickUp() {
            return "res/sounds/classical/pick.wav";
        }

        @Override
        public String getSoundNewLevel() {
            return "res/sounds/classical/success.wav";
        }

        @Override
        public String getSoundFail() {
            return "res/sounds/fail.wav";
        }
    },
    UNHEALTHY {
        @Override
        public String getSoundPickUp() {
            return "res/sounds/unhealthy/quick_fart.wav";
        }

        @Override
        public String getSoundNewLevel() {
            return "res/sounds/unhealthy/toilet_flush.wav";
        }

        @Override
        public String getSoundFail() {
            return "res/sounds/unhealthy/vomiting.wav";
        }
    };

    public abstract String getSoundPickUp();
    public abstract String getSoundNewLevel();
    public abstract String getSoundFail();
}

So, I've just defined the enum you see above. In the class, which is holding all configuration, is just one attribute like this:

private SoundPack soundPack = SoundPack.CLASSICAL;

Now, if I need to play some sound, I can get the path very simply:

configuration.getSoundPack().getSoundNewLevel();

Configuration might be changed very easily runtime just by assigning another value to the field soundPack. If sound isn't loaded yet (and there's chance they won't be as I'm using lazy loading a lot), changes will take affect immediately. Without changing anything else.

Also, if I'd want to add new sound pack, it would be done by defining new constant in that enum. Eclipse will show warning, I'll just press CTRL+1 and generate these methods. So it's also very easy.

I know that this is not the best way how to do that. But it's easy, it's quick and what's the most important: I wanted to try to use that in praxis. :-)



回答3:

[...] the method I just defined can't be called at all. Or am I wrong?

Correct, you're wrong: Java enums can implement interfaces, like this:

interface Bar {
    void myMethod();
}
enum Foo implements Bar {
    VALUE_1 {
        public void myMethod() {
            System.err.println("val1");
        }
    };
}

Now you have access to myMethod inside VALUE_1. Of course you would be forced to implement this method in other values, or in the enum itself. In addition, you could always access such methods, inaccessible through the language, through reflection.

As far as public variables are concerned, it looks like reflection is the only way here. Still, there's no reason to ban this outright (although it's hard to imagine a useful application for them).

Did you use such a functionality somewhere?

I did use an enum that implemented an interface, with each constant implementing the methods of the interface in its specific way.



回答4:

From the JLS

The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type.

Creating something like this

VALUE_2 {
    public void myMethod() {
        //
    }
},

when there is no myMethod() declared in the Enum itself (or its supertypes) just makes a method scoped to the abstract class, ie. can't be called outside of that body. Similar behavior applies to a field declared inside this body. The public identifier doesn't change anything.

Edit

For 3rd question, because what you're doing is declaring an anonymous class, no other component will have access to (to implement) the interface. A class on the other hand provides behavior and therefore can be used within the anonymous class.

Check out some restrictions on Anonymous classes.

End Edit

As for 4. I've never seen such code in practice. It can be useful if you want some helper method to perform some specific behavior only relevant for that specific enum. That would be very weird and would probably be better suited for a real class.

Take a look at the singleton pattern implemented with an enum. You might want to have many singletons with each having their own implementation. Anonymous enum class declaration with overriden methods could be a way to accomplish this.