为什么一个类不能扩展枚举?

15

我想知道为什么在Java语言中,一个class不能扩展一个enum

我不是在讨论一个enum扩展另一个enum的情况(这是不可能的,因为Java没有多重继承,并且enum隐式地扩展了java.lang.Enum),而是一个class扩展一个enum以添加额外的方法,而不是额外的枚举值。

类似于:

enum MyEnum
{
    ASD(5),
    QWE(3),
    ZXC(7);
    private int number;
    private asd(int number)
    {
        this.number=number;
    }
    public int myMethod()
    {
        return this.number;
    }
}

class MyClass extends MyEnum
{
    public int anotherMethod()
    {
        return this.myMethod()+1;
    }
}

要这样使用:

System.out.println(MyClass.ASD.anotherMethod());

那么,有人能够为这个限制提供合理的解释(或者指导我去正确的JLS章节)吗?


1
因为枚举默认是final的,所以你无法从一个final类继承。 - Luiggi Mendoza
7
他想知道为什么它们(指某些事物)是最终的——它们必须或应该是最终的原因是什么。 - user949300
@user949300,可以使用像Google这样的SEO工具很容易地找到这个。 - Luiggi Mendoza
为什么不直接编写一个静态方法,该方法接受一个 MyEnum 参数呢? - Louis Wasserman
1
@LouisWasserman 是的,有很多解决方法,但我很好奇为什么选择不允许按照我展示的方式去做 :) - Unai Vivi
6个回答

12
您无法扩展一个枚举类型。它们隐式地是final的。来自JLS § 8.9

除非至少包含一个具有类体的枚举常量,否则枚举类型隐式地是final的。

另外,来自JLS §8.1.4 - 超类和子类

如果ClassType命名Enum类或任何调用它,则会在编译时出错。

基本上,enum是一组预定义常量的枚举集合。因此,该语言允许您在switch-cases中使用枚举。如果允许扩展它们,例如,它们将成为适合于switch-cases的类型。除此之外,类或其他扩展枚举的枚举的实例也将成为您扩展的枚举的实例。这基本上破坏了enum的目的。

3
当然有影响,否则它就可以编译。为什么规范会这样说呢?在编写JLS时,他们并不是随意做出选择的,大多数(如果不是全部)决策都有充分的理由。这就是我要求了解其背后原理的原因。 - Unai Vivi
@UnaiVivi,也许你应该给James Gosling发电子邮件询问,而不是在这里提问:)。你只会得到一些简单的意见。 - Luiggi Mendoza
2
@LuiggiMendoza 抱歉,他已经收到了两个好的理由,而不是意见,解释为什么事物被设计成这样。这是一个情况,上帝(即Gosling)在创造宇宙时没有选择。 (请谷歌爱因斯坦获取原始引用) - user949300
1
@user949300 自Java 7以来,您还可以在普通的字符串上使用switch,因此我认为switch的原因不成立。此外,枚举类型根据定义是常量,常量不应被扩展或修改,这是我认为使用枚举类型更强有力的原因。 - Luiggi Mendoza
@RohitJain [关于您的编辑] 好吧,在我的想象中,扩展枚举的class不能添加额外的枚举值(只由enum提供),因此switch仍将操作原始枚举的常量,但是枚举将携带扩展类提供的额外方法。 - Unai Vivi
显示剩余5条评论

8
在Java 1.5之前的古老时代,你可能会这样定义枚举类型:
public class MyEnum {

public static final MyEnum ASD = new MyEnum(5);
public static final MyEnum QWE = new MyEnum(3);
public static final MyEnum ZXC = new MyEnum(7);

private int number;

private MyEnum(int number) {
    this.number = number;
}

public int myMethod() {
    return this.number;
    }

} 

这幅图有两个重要的内容:
  • 私有构造函数,不允许从外部实例化该类
  • 实际的“枚举” 值存储在静态字段中
即使您扩展它时它不是final,您会发现编译器需要一个显式构造函数,而这个构造函数又需要调用超类构造函数,但由于超类构造函数是私有的,因此无法调用。另一个问题是,超类中的静态字段仍然存储该超类的对象,而不是您扩展的对象。我认为这可能是一个解释。

5
枚举的整个目的就是创建一个“封闭”的可能值集合。这使得你作为程序员更容易理解该枚举类型的值——编译器也更容易处理(例如,在switch语句中高效地处理枚举)。允许类扩展枚举会打开可能值的集合;此时,枚举对你有什么好处呢?普通的类不是同样可以实现吗?

在我的想象中,如果class继承枚举,则不能添加额外的枚举值(只能由实际的enum提供),因此常量集仍将保持封闭和未扩展状态,但是枚举将携带扩展类提供的额外方法。 - Unai Vivi
1
你可以在枚举中定义抽象方法,并使每个枚举值成为实现该方法的匿名类。但是,如果你想要一个命名的类,那么如果不扩展“enum”值,你该如何实例化它呢? - yshavit

3

我认为回答“他们为什么这样做”的问题在于以下的问题:

以你的例子,如果想要实例化MyClass,你会怎么做?枚举类型永远不会被用户通过new MyEnum()进行显式实例化。你需要做一些类似于MyClass.ASD的操作,但是我不确定这个操作是否可行。

基本上,我不知道你提出的补充语法是否有效。这可能就是为什么他们将它们定义为final等原因...

编辑已添加

如果原始枚举的作者事先计划好了(不太可能),并且您不太担心线程安全性,那么您可以尝试这样做:(顺便说一句,如果有人真的在生产代码中这样做,我可能会大声抗议,因人而异)

public enum ExtendibleEnum {

   FOO, BAR, ZXC;

   private Runnable anotherMethodRunme;  // exact Interface will vary, I picked an easy one
                           // this is what gets "injected" by your other class

   public void setAnotherMethodRunMe(Runnable r) {  // inject here
      anotherMethodRunme= r;
   }

   public void anotherMethod() {  // and this behavior gets changed
      anotherMethodRunme.run();
   }
}

1
我本来打算把你的回答设为被采纳的答案……但是后来我看到了你的编辑,然后就尖叫了一下 ^_^ - Unai Vivi
...但现在,多亏了你,我对函数指针和传递代码块的想法感到很有兴趣 :) - Unai Vivi
我通常对这种事情很克制,但是我一直在阅读并享受《让Java变得优雅》。所以我把这归咎于最近让我更加“函数化”... :-) - user949300


0
枚举的整个目的是创建一组可能值的封闭集。这使得更容易推理该枚举类型的值是什么,因此常量集仍将保持封闭和未扩展状态,但枚举将携带扩展类提供的额外方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接