Java:枚举常量中方法和变量的定义

时间:2013-08-23 00:32:42

标签: java oop inheritance enums language-design

我正在做一些实验并且意外地编写了一个代码,这非常奇怪而且我不能完全理解。我甚至感到惊讶,我可以编译它。它看起来像这样:

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

正如预期的那样,不可能通过以下方式访问这样的元素:

Foo.VALUE_2.myMethod();

原因是,编译器将在枚举本身内查找该方法。

我认为不可能从枚举之外访问这些方法和变量。出于这个原因,我尝试创建一个参数化构造函数并使用一些内部变量调用它:

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

    private Foo(int param) {
        //
    }
}

编译这样的结构是不可能的。现在我在想如果没有办法访问它,那么在常量中定义一些内容是什么意思。

我试图在常量中创建同名的方法以及枚举本身,以检查它是否以某种方式发生碰撞。它没有!

enum Foo {
    VALUE_1 {
        int myVariable = 1;

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

    public int myMethod() {
        return 0;
    }
}

这是有趣的时刻!我试图在枚举中继续调用myMethod()并实际找出这个Java魔法是如何工作的。方法,在常量内定义,覆盖枚举中定义的方法。

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

但是,我们无法覆盖变量,对吧?所以我很好奇,它只适用于变量。

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

现在我终于回答了我的问题:

  1. 如果我在常量和左枚举中创建公共方法而没有此方法,为什么我不会收到任何错误?在这种情况下,我刚刚定义的方法根本不能被称为。或者我错了吗?

    更新:我知道枚举可以实现接口。但是,如果我没有 具体说来,整个代码毫无意义。

    有人指出,即使方法无法从正常的语言中访问 方式,它仍然可以使用反射。嗯......为什么我们不设计无法访问 关键字?

    inaccessible void magicalMethod() {
         //
    }
    

    这样的方法将被编译到* .class文件中。如果你想使用它,你必须这样做 自己加载字节码并解释它。

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

    我认为这最终会导致错误,而不仅仅是警告未使用的方法。你可能会忘记 添加“implement”子句或在枚举中定义方法(可能是 被覆盖的)并且会在第一次使用后才意识到。 Java是非常严格的语言, 所以我期待这种行为。

  2. 如果我在常量中创建公共变量(或更精确的字段),为什么我不会收到任何错误?在任何情况下(从外部)都无法访问它。因此,修饰语“public”在这里没有任何意义。

    更新:除了可见性之外,它与上一点相同 修饰符在这里完全没用。它是否公开,受到保护真的无关紧要 或私人的,因为无论如何你都无法访问它。我认为这是一个错误。

  3. 为什么定义一个类(没有可见性修饰符),但不是接口?是的,你可能不想编写如此残酷的枚举,你需要在常量中定义类,甚至在那里使用继承。但是,如果可以定义类和抽象类,那似乎很奇怪。

    更新:这绝对不是您经常需要的,但我明白 它可能有用。但是为什么它只限于类而接口不能 定义也好吗?

    enum Foo {
        VALUE {
            class MyClass {
                // OK
            }
    
            abstract class MyAbstractClass {
                // OK
            }
    
            interface MyInterface {
                // FAIL. It won't compile.
            }
        }
    }
    
  4. 你在某个地方使用过这样的功能吗?我可以想象它可能有用,但它有点令人困惑。此外,当我在寻找一些资源时,我没有找到任何东西。

    更新:我想在enum常量类体中看到一些重写方法的实际示例。你有没有在一些开源项目中看到它?

  5. 环境:

    $ 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)
    

    感谢您的时间和答案!

4 个答案:

答案 0 :(得分: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并生成这些方法。所以它也很容易。

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

答案 1 :(得分:3)

  

[...]我刚刚定义的方法根本无法调用。或者我错了吗?

正确,你错了:Java enum可以实现接口,如下所示:

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

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

就公共变量而言,看起来反射是唯一的方法。尽管如此,没有理由完全禁止这种情况(虽然很难想象它们是一个有用的应用程序)。

  

你在某个地方使用过这样的功能吗?

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

答案 2 :(得分:2)

来自JLS

  

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

创建这样的东西

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

当Enum本身(或其超类型)中没有声明myMethod()时,只是将一个方法限定为抽象类,即。不能在那个身体之外被召唤。类似的行为适用于在此正文中声明的字段。 public标识符不会改变任何内容。

修改

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

在Anonymous类上查看一些restrictions

结束修改

至于4.我在实践中从未见过这样的代码。如果您希望某个帮助程序方法执行仅与该特定枚举相关的某些特定行为,则会很有用。这将是非常奇怪的,可能更适合真正的班级。

看看singleton pattern implemented with an enum。您可能希望拥有许多单身人士,每个单身人士都有自己的实施方案。使用覆盖方法的匿名枚举类声明可以是实现此目的的一种方法。

答案 3 :(得分:2)

  

如果我在常量中创建公共方法并且在没有此方法的情况下将左枚举空为空,为什么我不会收到任何错误?在那种情况下,我刚刚定义的方法根本无法调用。或者我错了吗?

实际上编译器应该能够看到该方法在枚举常量的类体外部是不可见的,并警告你是否未使用它 - 我确信Eclipse会这样做。作为dasblinkenlight points out,这样的公共方法实际上可能是枚举实现的接口声明的方法的覆盖。

  

我无法理解,为什么可以定义无法访问的方法。我能想到的唯一原因是程序员正在工作,还没有接口的定义。所以他只是准备单个方法的代码,稍后会添加“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!
    }
}

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

  

你在某个地方使用过这样的功能吗?我可以想象它可能有用,但它有点令人困惑。此外,当我在寻找一些资源时,我没有找到任何东西。

目前还不清楚你在这里提到的具体功能。请更新问题以指定您正在寻找的内容 - 枚举常量类主体中的重写方法?场?帮助方法?助手班?请澄清。