我正在探索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());
    }
}

使用此 struct ,我可以链接方法调用,如下所示:

Dog dog = new DogBuilder().setName("Fido").setBreed("Husky").build();

我可以使用传统的继承和方法覆盖来达到类似的结果,方法是覆盖DogBuilder内的setName个返回类型,尽管这会涉及到在子类中复制代码.

所以我的问题是:递归泛型可以被看作是一种更优雅和类型安全的子类化形式,方法重写吗?或者在某些特定的情况下,递归泛型提供了子类化和重写所不能提供的功能?

推荐答案

在这种编写构建器的情况下,使用递归泛型参数(也就是CRTP)只是为了避免调用方的大量强制转换.也就是说,如果您首先调用超类中的方法之一,则需要将其强制转换为子类以继续构建子类的字段,例如

(
    (DogBuilder)new DogBuilder().setName("Foo") // setName() makes it an AnimalBuilder
).setBreed("Bar").build()

不过,我不会把它叫做"句法糖".语法糖通常指的是只需lowers个代码的语言功能.这里没有贬低的意思.编译器does在其可能的情况下在这里强制执行泛型.例如,你不能把DogBuilder传递给期望AnimalBuilder<CatBuilder>的东西.

还请注意,使用CTRP时,在传递AnimalBuilder时必须提供泛型类型参数.有人可能会说,这是一个轻微的不便.

不过,一般而言,CRTP可以做的不仅仅是简单的继承.这基本上是Java中缺少"self"类型的解决方案.如果您想要一个方法总是接受一个"不管这是什么子类"的实例,您可以这样做

abstract class Animal<TSelf extends Animal<TSelf>> {
    public abstract TSelf makeBabiesWith(TSelf other);
}

这至少为您提供了一定程度的编译时安全性.人们不会意外地错过你意想不到的东西(尽管如果他们真的想要的话,他们仍然可以).如果你用简单的继承来做到这一点,

abstract class Animal {
    public abstract Animal makeBabiesWith(Animal other);
}


class Dog {
    public abstract Dog makeBabiesWith(Animal other) {
        // you must check this:
        if (other instanceof Dog) {
            // ...
        } else {
            throw new Exception(); // or something similar
        }
    }
}

人们可能会意外地传入非DogDog.makeBabiesWith的值,而编译器对此完全没有意见.


我还建议看看龙目鱼的@SuperBuilder是如何工作的.它使用两个递归泛型类型参数,如下所示.一个表示"当前构建器类型",如您所示,另一个表示"这个构建器正在构建什么",以实现toBuilder特性.

Java相关问答推荐

int Array Stream System. out. print方法在打印Java8时在末尾添加% sign

查找最大子数组的和

同时运行JUnit测试和Selenium/Cucumber测试时出现问题

如何调用Firebase Realtime Database中的子图像列表到android studio中的回收器视图?

Java函数式编程中的双值单值映射

在模拟超类中设置非setter属性的值

Spring Boot@Cachebale批注未按预期工作

是否在允许数组元素为空时阻止 idea 为空性警告?

具有阻塞方法的开源库是否应该为执行提供异步选项?

我如何解释这个错误?必需类型:供应商R,提供:收集器对象,捕获?,java.util.List java.lang.Object>>

从ActiveMQ Classic迁移到ActiveMQ Artemis需要进行哪些客户端更改?

使用Jackson库反序列化json

没有使用Lombok生成的参数

在一行中检索字符分隔字符串的第n个值

如何在SWT菜单项文本中保留@字符

具有最大共同前景像素的图像平移优化算法

try 添加;按流派搜索;在Web应用程序上,但没有;I don’我不知道;It’这个代码错了

MapStruct记录到记录的映射不起作用

Springboot应用程序无法识别任何@RestController或@Service,我认为@Repository也无法识别

如何在 Android Studio 中删除 ImageView 和屏幕/父级边缘之间的额外空间?