为什么在Java中枚举被声明为Enum<E extends Enum<E>>?

50

可能是重复问题:
Java枚举定义

更好的表述问题,不被视为重复问题:
如果Java中的Enum声明没有递归部分会有什么不同

如果语言设计者使用简单的“Enum<E extends Enum>”,那会对语言产生什么影响?

现在唯一的区别是某人可以编写

A extends Enum<B>

但由于在Java中扩展枚举是不允许的,因此这仍然是非法的。我还考虑过某人向jvm提供字节码,将其定义为扩展枚举 - 但是泛型不能影响它们,因为它们都被擦除了。

那么这种声明的整个意义是什么呢?

谢谢!

编辑 为了简单起见,让我们看一个例子:

interface MyComparable<T> {
    int myCompare(T o);
}
class MyEnum<E extends MyEnum> implements MyComparable<E> { public int myCompare(E o) { return -1; } }
class FirstEnum extends MyEnum<FirstEnum> {}
class SecondEnum extends MyEnum<SecondEnum> {}

这个类结构有什么问题?可以通过什么方法使“MyEnum<E extends MyEnum<E>>”受到限制?


问题是完全重复的,是的 - 但是以前的问题没有令人满意的答案,恐怕。 - atamur
然后添加一条评论,询问您不理解的内容,或者(如果这是一个非常具体的问题)引用其他问题提出具体问题。 - Joachim Sauer
不需要再查找Enum的compareTo方法以获取答案。(在我能够给出完整解释之前,问题已关闭。) - tylermac
1个回答

45
这是一个常见的问题,可以理解。请查看泛型 FAQ中的这一部分以获取答案(实际上,尽可能多地阅读整个文档,它非常好且信息丰富)。
简短的答案是,它强制类在自身上进行参数化;这对于超类使用泛型参数定义方法,并且与其子类透明地(“本地”)工作是必需的。
编辑:例如,考虑Object上的clone()方法。目前,它被定义为返回类型为Object的值。由于协变返回类型,特定的子类可以(并经常)定义它们返回更具体的类,但这不能强制执行,因此无法推断出任意类的返回类型。
现在,如果Object像Enum一样被定义,即Object>,那么您必须将所有类定义为类似于public class MyFoo的东西。因此,clone()可以声明返回类型T,您可以在编译时确保返回的值始终与对象本身完全相同的类(甚至子类也不会匹配参数)。
在这种情况下,Object没有像这样进行参数化,因为当99%的类根本不使用它时,在所有类上都有这个负担非常让人烦恼。但是对于某些类层次结构,它非常有用-我以前使用过类似的技术,使用具有多个实现的抽象递归表达式解析器类型。这个构造使得可以编写“明显”的代码,而不必到处进行转换或复制和粘贴以更改具体的类定义。
编辑2(实际回答您的问题!):
如果Enum被定义为Enum,那么正如您所说,有人可以将类定义为A扩展Enum。这违背了泛型构造的目的,即确保泛型参数始终是所涉及类的确切类型。具体举例来说,Enum将其compareTo方法声明为
public final int compareTo(E o)

在这种情况下,由于你定义了A来扩展Enum<B>,因此A的实例只能与B的实例(无论B是什么)进行比较,这几乎肯定没有什么用处。有了附加结构,您就知道扩展Enum的任何类只能相互比较。因此,您可以在超类中提供方法实现,在所有子类中保持有用性和特定性。
(如果没有这个递归泛型技巧,唯一的其他选择将是将compareTo定义为public final int compareTo(Enum o)。这并不是完全相同的事情,因为然后一个可以将java.math.RoundingModejava.lang.Thread.State进行比较而不会出现编译器抱怨,这也没有什么用处。)
好的,让我们远离Enum本身,因为我们似乎已经卡在上面了。相反,这里是一个抽象类:
public abstract class Manipulator<T extends Manipulator<T>>
{
    /**
     * This method actually does the work, whatever that is
     */
    public abstract void manipulate(DomainObject o);

    /**
     * This creates a child that can be used for divide and conquer-y stuff
     */
    public T createChild()
    {
        // Some really useful implementation here based on
        // state contained in this class
    }
}

我们将会有几个具体的实现 - SaveToDatabaseManipulator,SpellCheckingManipulator等等。此外,我们还希望让人们定义自己的实现,因为这是一个非常有用的类。;-)
现在 - 你会注意到我们正在使用递归泛型定义,然后从createChild方法返回T。这意味着:
1)我们知道并且编译器知道如果我调用:
SpellCheckingManipulator obj = ...; // We have a reference somehow
return obj.createChild();

如果方法返回一个SpellCheckingManipulator,即使它是使用超类的定义,返回值仍然是明确的SpellCheckingManipulator。这里的递归泛型使编译器能够知道对我们来说很明显的东西,因此您不必像例如clone()一样经常强制转换返回值。

2) 请注意,我没有将该方法声明为final,因为也许某些特定的子类想要用更适合自己的版本覆盖它。泛型定义意味着无论谁创建一个新类或如何定义它,我们仍然可以断言例如BrandNewSloppilyCodedManipulator.createChild()的返回值仍然是BrandNewSloppilyCodedManipulator的实例。如果粗心的开发人员尝试将其定义为只返回Manipulator,编译器不会让他们这样做。如果他们尝试将类定义为BrandNewSloppilyCodedManipulator<SpellCheckingManipulator>,编译器也不会让他们这样做。

基本上,结论是当您想在超类中提供一些功能,并且这些功能在子类中变得更具体时,这个技巧非常有用。通过这种方式声明超类,您可以将任何子类的泛型参数锁定为子类本身。这就是为什么您可以在超类中编写一个通用的compareTocreateChild方法,防止它在处理特定子类时变得过于模糊。


1
首先 - 枚举并不是魔法。你不能直接扩展它,但一旦创建,它仍然是一个普通的类,泛型定义仍然必须匹配(我假设在将所有内容传递给javac之前会发生代码生成步骤。也许它可以是魔法。 :-)). 此外,正如我提到的,Enum并不是唯一能够做到这一点的类。我以前在自己的类上使用过这个方法,效果很好。 - Andrzej Doyle
那么子类可以打破方法的语义,但是根据JLS,它们不能扩展枚举!那么这样做的唯一原因是语言设计者不信任javac / jls吗?这听起来不合理。 - atamur
你现在有一个类A,它不能与自身进行比较!- 你能举个例子吗? - atamur
谢谢你的示例!如果你用javac检查它,你会发现有时你认为的并不是真的。也就是说,它不会阻止你定义BrandNewSloppilyCodedManipulator<SpellCheckingManipulator>。然后createChild可以正常工作,没有递归的部分-试试吧!-它仍然不允许你返回Manipulator而不是子类。所以我还是不明白它对枚举有什么用处。我创建了一个新问题,措辞更加严格,这样它就不会被关闭为重复问题-很高兴在那里见到你。 - atamur
@AndrzejDoyle 如果我可以的话,我会给你1000个赞,因为这是一个非常棒的解释。 - benz
显示剩余9条评论

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