这是一个常见的问题,可以理解。请查看
泛型 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.RoundingMode
与java.lang.Thread.State
进行比较而不会出现编译器抱怨,这也没有什么用处。)
好的,让我们远离Enum
本身,因为我们似乎已经卡在上面了。相反,这里是一个抽象类:
public abstract class Manipulator<T extends Manipulator<T>>
{
public abstract void manipulate(DomainObject o);
public T createChild()
{
}
}
我们将会有几个具体的实现 - 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>
,编译器也不会让他们这样做。
基本上,结论是当您想在超类中提供一些功能,并且这些功能在子类中变得更具体时,这个技巧非常有用。通过这种方式声明超类,您可以将任何子类的泛型参数锁定为子类本身。这就是为什么您可以在超类中编写一个通用的compareTo
或createChild
方法,防止它在处理特定子类时变得过于模糊。