我正在探索 Java 中的递归泛型的概念,并试图了解它相对于传统继承和方法重写的好处。具体来说,我想知道递归泛型是否可以被视为具有方法重写的子类的“语法糖”,我的意思是,这两种方法在功能上是否等效?
例如,考虑使用递归泛型实现的构建器模式:
public abstract class AnimalBuilder<T extends AnimalBuilder<T>> {
private String name;
public T setName(String name) {
this.name = name;
return (T) this;
}
public Animal build() {
return new Animal(name);
}
}
public class DogBuilder extends AnimalBuilder<DogBuilder> {
private String breed;
public DogBuilder setBreed(String breed) {
this.breed = breed;
return this;
}
public Dog build() {
return new Dog(breed, super.build().getName());
}
}
通过这种结构,我可以链接方法调用,例如:
Dog dog = new DogBuilder().setName("Fido").setBreed("Husky").build();
我可以使用传统继承和方法重写来实现类似的结果,通过重写
setName
中的 DogBuilder
返回类型,尽管这会涉及在子类中重复代码。
所以我的问题是:递归泛型是否可以被视为一种更优雅且类型安全的具有方法重写的子类形式?或者是否存在递归泛型提供子类化和重写无法提供的功能的特定情况?
任何见解或参考将不胜感激。
在编写构建器的情况下,使用递归泛型参数(又名CRTP)只是为了避免调用方进行大量转换。也就是说,如果您首先调用超类中的方法之一,则需要将其转换为子类以继续构建子类的字段,例如
(
(DogBuilder)new DogBuilder().setName("Foo") // setName() makes it an AnimalBuilder
).setBreed("Bar").build()
不过我不会称其为“语法糖”。语法糖通常指的是简单地降低代码的语言功能。这里没有降低。编译器会尽可能强制执行泛型。例如你不能将 DogBuilder
传递给需要
AnimalBuilder<CatBuilder>
的东西。另请注意,使用 CTRP,在传递 AnimalBuilder
时必须提供泛型类型参数。有人可能会说这有点不便。
一般来说,CRTP 能做的不仅仅是简单的继承。它基本上是 Java 中缺少“self”类型的解决方法。如果您希望一个方法始终采用“无论这是什么子类”的实例,您可以这样做abstract class Animal<TSelf extends Animal<TSelf>> {
public abstract TSelf makeBabiesWith(TSelf);
}
你不能仅通过继承来做这样的事情。
我还建议看看 lombok 的
是如何工作的。它使用两个像这样的递归泛型类型参数。一个代表“当前构建器类型”,如您所示,另一个代表“此构建器正在构建什么”,以实现
toBuilder
功能。