java - 为什么在 java 枚举中声明为 Enum<E extends Enum<E>>

标签 java enums

这个问题在这里已经有了答案:




11 年前关闭。




Possible Duplicate:
java Enum definition



Better formulated question, that is not considered a duplicate:
What would be different in Java if Enum declaration didn't have the recursive part



如果语言设计者只使用 Enum 会如何影响语言?

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

A 扩展 Enum

但是因为在java中不允许扩展仍然是非法的枚举。
我也在考虑有人向 jvm 提供一个字节码,该字节码将 smth 定义为扩展枚举 - 但泛型不能影响它,因为它们都被删除了。

那么这种声明的全部意义是什么?

谢谢!

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

接口(interface) MyComparable {
int myCompare(T o);
}

class MyEnum 实现 MyComparable {
公共(public) int myCompare(E o) { return -1; }
}

class FirstEnum 扩展 MyEnum {}

class SecondEnum 扩展了 MyEnum {}

这个类结构有什么问题? “MyEnum>”会限制什么?

最佳答案

这是一个常见的问题,这是可以理解的。看看this part of the generics FAQ对于答案(实际上,请尽可能多地阅读整个文档,因为它做得很好并且内容丰富)。

简短的回答是它强制类对自身进行参数化;这是父类(super class)使用泛型参数定义方法所必需的,这些方法与其子类透明地(“本地”,如果您愿意的话)。

编辑:例如,作为(非)示例,请考虑 clone()方法在 Object .目前,它被定义为返回类型 Object 的值。 .由于协变返回类型,特定的子类可以(并且经常这样做)定义它们返回一个更特定的类,但这不能强制执行,因此不能为任意类推断。

现在,如果 对象的定义类似于 Enum,即 Object<T extends Object<T>>那么你必须将所有类定义为类似 public class MyFoo<MyFoo> .因此,clone()可以声明为返回类型 T并且您可以在编译时确保返回值始终与对象本身完全相同(甚至子类都不匹配参数)。

现在在这种情况下,Object 没有像这样参数化,因为当 99% 的类根本不会使用它时,在所有类上都有这个包袱会非常烦人。但是对于某些类层次结构,它可能非常有用——我自己之前也使用过类似的技术来处理具有多种实现的抽象递归表达式解析器的类型。这种结构使得编写“显而易见”的代码成为可能,而不必到处进行强制转换,或者只是为了更改具体的类定义而进行复制和粘贴。

编辑 2 (实际回答您的问题!):

如果枚举被定义为 Enum<E extends Enum> ,那么正如你所说的那样,有人可以将一个类定义为 A extends Enum<B> .这违背了泛型构造的要点,即确保泛型参数始终是相关类的确切类型。举一个具体的例子,Enum 将它的 compareTo 方法声明为

public final int compareTo(E o)

在本例中,由于您定义了 A延长 Enum<B> , A 的实例只能与 B 的实例进行比较(无论 B 是什么),这几乎肯定不是很有用。通过附加构造,您知道 任何 扩展 Enum 的类只能与自身进行比较。因此,您可以在父类(super class)中提供在所有子类中仍然有用且特定的方法实现。

(没有这个递归泛型技巧,唯一的其他选择是将 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 等等。此外,我们还想让人们定义自己的类,因为这是一个非常有用的类。 ;-)

现在 - 你会注意到我们正在使用递归泛型定义,然后返回 T来自 createChild方法。这意味着:

1) 我们知道 并且编译器知道 如果我打电话:
SpellCheckingManipulator obj = ...; // We have a reference somehow
return obj.createChild();

那么返回的值肯定是SpellCheckingManipulator ,即使它使用的是父类(super class)的定义。这里的递归泛型允许编译器知道什么对我们来说是显而易见的,因此您不必继续转换返回值(例如,您经常需要处理 clone() )。

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

基本上,结论是当您想在父类(super class)中提供一些在子类中变得更具体的功能时,这个技巧很有用。通过像这样声明父类(super class),您是 锁定 任何子类的泛型参数都是子类本身。这就是为什么你可以写一个通用的 compareTocreateChild方法并防止它在处理特定子类时变得过于模糊。

关于java - 为什么在 java 枚举中声明为 Enum<E extends Enum<E>>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3061759/

相关文章:

c# - 拖放效果.滚动 : why -2147483648 not 8?

c++ - C++ 枚举中的最大值和最小值

ruby-on-rails - 如何动态获取枚举的字符串值?

java - JPA 连接在 Eclipse 中工作,但在编译时给出 NullPointerException

java - 调用bean方法primefaces jsf时托管bean字段为空

java - Hazelcast - 通用 map 配置

java - 反序列化 Enum,其中包含 Map

java - 根据按下的按钮加载包

java - 内存缓存+ Spring 缓存

c - 将 typedef 枚举初始化为字符串