【问题标题】:Why in java enum is declared as Enum<E extends Enum<E>> [duplicate]为什么在 java 中枚举被声明为 Enum<E extends Enum<E>> [重复]
【发布时间】:2010-06-17 12:55:40
【问题描述】:

可能重复:
java Enum definition

更好的问题,不被视为重复:
What would be different in Java if Enum declaration didn't have the recursive part

如果语言设计者只使用 Enum 会对语言产生什么影响?

现在唯一的区别是有人会写

A 扩展枚举

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

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

谢谢!

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

接口 MyComparable { int myCompare(T o); } 类 MyEnum 实现 MyComparable { public int myCompare(E o) { return -1; } } 类 FirstEnum 扩展 MyEnum {} 类 SecondEnum 扩展 MyEnum {}

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

【问题讨论】:

  • qn 是完全重复的,是的 - 但我担心 prev qn 没有满意的答案
  • 然后在此处添加评论,要求澄清您不理解的内容(如果这是一个非常具体的问题)提出具体问题,并参考其他问题。
  • 无需再看 Enum 上的 compareTo 方法即可得到答案。 (我还没来得及给出完整的解释,问题就结束了。

标签: java enums


【解决方案1】:

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

简短的回答是它强制类在自身上进行参数化;这是超类使用泛型参数定义方法所必需的,这些方法与它们的子类透明地工作(“本机”,如果你愿意的话)。

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

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

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

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

如果 Enum 被定义为Enum&lt;E extends Enum&gt;,那么正如您所说的,有人可以将一个类定义为A extends Enum&lt;B&gt;。这违背了泛型构造的要点,即确保泛型参数始终是所讨论类的 exact 类型。举一个具体的例子,Enum 将它的 compareTo 方法声明为

public final int compareTo(E o)

在这种情况下,由于您将A 定义为扩展Enum&lt;B&gt;,因此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,即使它使用的是超类中的定义。这里的递归泛型允许编译器知道什么对我们来说是显而易见的,因此您不必继续强制转换返回值(例如,您经常需要处理 clone())。

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

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

【讨论】:

  • 我已经阅读了那个常见问题解答(应该在我的 qn 中提到过),但它没有回答 my 的问题! “这是超类使用泛型参数定义方法所必需的””则无法使用的此类方法?
  • 写给你的例子:据我所知,这可以通过将 Object 定义为 Object 来实现。不需要额外的递归 - 这就是我问这个问题的原因!
  • 感谢您的时间,但至于您的第二次编辑:我在我的 qn 中声明这种情况是不可能的,因为除了 javac 之外没有任何东西可以扩展枚举,而当 javac 实际上扩展了所有枚举时泛型已经被删除,所以这看起来不是真正的原因。
  • 并重申:没有递归技巧,您仍然可以定义 compareTo(E o) 完美地完成任务!
  • 首先 - 枚举不是魔法。你不能直接扩展它,但是一旦创建它仍然是一个普通的类并且泛型定义仍然必须匹配(我在这里假设代码生成步骤发生在像往常一样将所有内容传递给javac之前。也许它可能是魔法。:-))。此外,正如我所提到的,Enum 并不是唯一可以这样做/可以这样做的类。我以前在自己的课程中使用过这个,效果很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-17
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 2016-12-11
  • 1970-01-01
相关资源
最近更新 更多