10

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!

4

4 回答 4

4

好的,我实际上已经使用了这个功能!我正在编写简单的游戏,并想提供两个声音包。因为这个游戏很简单,以后可能不会再扩展了,所以我不想创造一些复杂的机制来实现这样的事情。

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

所以,我刚刚定义了你在上面看到的枚举。在包含所有配置的类中,只有一个属性,如下所示:

private SoundPack soundPack = SoundPack.CLASSICAL;

现在,如果我需要播放一些声音,我可以非常简单地获得路径:

configuration.getSoundPack().getSoundNewLevel();

只需为字段 soundPack 分配另一个值,就可以很容易地在运行时更改配置。如果声音还没有加载(并且有可能他们不会因为我经常使用延迟加载),更改将立即生效。在不改变任何其他东西的情况下。

另外,如果我想添加新的声音包,可以通过在该枚举中定义新常量来完成。Eclipse 将显示警告,我只需按 CTRL+1 并生成这些方法。所以这也很容易。

我知道这不是最好的方法。但这很容易,很快,最重要的是:我想尝试在实践中使用它。:-)

于 2013-08-23T18:37:32.803 回答
3

[...] 我刚刚定义的方法根本无法调用。还是我错了?

对,你错了:Javaenum可以实现接口,像这样:

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

现在您可以访问myMethodinside VALUE_1。当然,您将被迫在其他值或enum本身中实现此方法。此外,您始终可以通过反射访问这些无法通过语言访问的方法。

就公共变量而言,看起来反射是唯一的方法。尽管如此,没有理由彻底禁止这一点(尽管很难想象对他们有用的应用程序)。

您是否在某处使用过这样的功能?

我确实使用了一个enum实现接口的接口,每个常量都以特定的方式实现接口的方法。

于 2013-08-23T00:46:49.587 回答
3

如果我在常量内创建公共方法并且在没有此方法的情况下将枚举留空,为什么我不会收到任何错误?那样的话,我刚才定义的方法根本就不能调用。还是我错了?

事实上,编译器应该能够看到该方法在枚举常量的类主体之外是不可见的,并且如果它没有被使用就会警告你——我确信 Eclipse 会这样做。正如 dasblinkenlight指出的那样,这样的公共方法实际上可能是对由枚举实现的接口声明的方法的覆盖。

我只是无法理解,为什么可以定义无法访问的方法。我能想到的唯一原因是程序员正在工作并且还没有定义接口。所以他只是在准备单个方法的代码,稍后会添加“implements”关键字。除此之外,这是不合逻辑的,它仍然需要在所有常量中都有这样的方法。

正如我已经指出的,这并不特别适用于枚举常量类。有许多范围——私有嵌套类、本地类、匿名类——在这些范围内,成员公开是没有意义的。

这个问题的问题在于,只有语言设计者才能真正回答它。我只能给出我的看法,那就是:为什么会是错误?语言规范不是免费提供的——JLS 中的所有内容都必须经过精心定义,然后实施和测试。真正的问题是,让它成为错误有什么好处?唯一的事实是,虽然未使用的成员可能表示错误(因此发出警告),但它并没有伤害任何东西。

如果我在常量内创建公共变量(或更准确地说是字段),为什么我没有收到任何错误?在任何情况下(从外部)都无法访问它。因此,修饰符“public”在这里没有任何意义。

与上面相同 - 如果未使用变量,编译器或至少某些 IDE 会警告您。这就像您public在嵌套类中声明了一个变量private然后没有在任何地方引用它一样。无论如何,禁止这种情况并不是 JLS 的首要任务,尽管反思的眼睛看到了一切。

它与前一点不太一样,除了可见性修饰符在这里完全没用。它是公共的、受保护的还是私有的并不重要,因为无论如何你都无法访问它。我认为这是一个错误。

在这里,您忘记了成员可能仍会在枚举常量类主体中使用 - 例如考虑一个辅助方法。只是在这种情况下,访问修饰符根本无关紧要,可以省略。

为什么可以定义一个类(没有可见性修饰符),但不能定义接口?是的,您可能不想编写如此残酷的枚举,以至于您需要在常量内定义类,甚至在那里使用继承。但是如果可以定义类和抽象类,就显得有点奇怪了。

这是一个很好的问题,我花了一段时间才明白你的意思。为了澄清,你是说在这种情况下只允许类:

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

为了阐明这一点,请尝试制作课程static

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

现在两者都不被允许。为什么?static枚举常量主体中不能声明任何内容,因为主体位于该常量实例的上下文中。这类似于在内部(非静态嵌套)类的范围内:

class Outer {

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

静态变量、方法、类和接口(嵌套时是隐式静态的)在这样的范围内都是被禁止的。

您是否在某处使用过这样的功能?我可以想象它可能有用,但它有点令人困惑。另外,当我搜索有关此的一些资源时,我什么也没找到。

目前尚不清楚您在这里具体指的是什么功能。请更新问题以指定您正在寻找的确切内容 - 枚举常量类主体中的覆盖方法?领域?辅助方法?辅助类?请说清楚。

于 2013-08-23T01:13:22.953 回答
2

来自JLS

枚举常量的可选类主体隐式定义了一个匿名类声明(第 15.9.5 节),该声明扩展了直接封闭的枚举类型。

创造这样的东西

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

myMethod()当枚举本身(或其超类型)中没有声明时,只需将方法限定为抽象类,即。不能在那个身体之外被调用。类似的行为适用于在此主体内声明的字段。public标识符不会改变任何东西。

编辑

对于第三个问题,因为您正在做的是声明一个匿名类,所以没有其他组件可以访问(实现)该接口。另一方面,类提供行为,因此可以在匿名类中使用。

查看匿名类的一些限制

结束编辑

至于4。我在实践中从未见过这样的代码。如果您想要一些辅助方法来执行一些仅与该特定枚举相关的特定行为,它会很有用。那会很奇怪,可能更适合真正的课程。

看看使用 enum 实现的单例模式。您可能希望拥有许多单例,每个单例都有自己的实现。具有重写方法的匿名枚举类声明可能是实现此目的的一种方法。

于 2013-08-23T00:42:48.147 回答