Java 是否存在推断协方差
我正在将一些代码重构为构建器模式,并在为子类对构建器进行子分类时遇到了一个问题 当我有一个生成器子类,并且我尝试在父类中链接一个方法时,它将返回父类生成器,因此我的实例不再具有访问子类方法的权限Java 是否存在推断协方差,java,oop,subclass,covariance,Java,Oop,Subclass,Covariance,我正在将一些代码重构为构建器模式,并在为子类对构建器进行子分类时遇到了一个问题 当我有一个生成器子类,并且我尝试在父类中链接一个方法时,它将返回父类生成器,因此我的实例不再具有访问子类方法的权限 public class App { public static void main(String[] args) { Parent p; p = new App().new ChildBuilder() .withName("Test"
public class App {
public static void main(String[] args) {
Parent p;
p = new App().new ChildBuilder()
.withName("Test")
.withNickName("Test1")
.build();// Doesn't Compile
p = new App().new ChildBuilder()
.withNickName("Test1")
.withName("Test")
.build();
}
class Parent {
public Parent(ParentBuilder builder) {}
}
class Child extends Parent {
public Child(ChildBuilder builder) { super(builder); }
}
class ParentBuilder {
private String name;
public ParentBuilder() {}
public Parent build() { return new Parent(this); }
public ParentBuilder withName(String name) {
this.name = name;
return this;
}
}
class ChildBuilder extends ParentBuilder {
private String nickName;
public ChildBuilder withNickName(String nickName) {
this.nickName = nickName;
return this;
}
}
}
main方法中的第二行不会编译,因为withName(“Test”)
位于ParentBuilder类上,并返回ParentBuilder。重新排列链以调用所有ChildBuilder方法首先解决了问题,但对于使用我的api(包括我自己)的人来说,这听起来是一次可怕的经历。如果我在子对象中添加覆盖,我可以通过协方差使其工作:
@Override
public ChildBuilder withName(String name) {
super.withName(name);
return this;
}
但这是我不愿意维护的大量样板代码(每个父构建器可能有几个子类,因此我需要在每个子类中为父类中的每个方法重写这些方法)
有没有一种方法可以在没有覆盖的情况下完成我想做的事情?java能“推断”子类中的协变方法吗
我还担心这个问题表明我设计的构建器不正确。不,没有推断出协方差,但它被奇怪的重复模板模式(或CRTP,起源于C++)模仿 您可以通过添加2个使用CRTP(即参数类型为子类)的(包私有)抽象类来解决这个问题。生成器功能将移动到这些类,然后创建两个扩展抽象生成器的空类 我还更改了构造函数,使其不直接依赖于生成器,而是依赖于实际参数,因为这通常是如何完成的,这使这里的实现更简洁:
public static void main(String[] args) {
// Both examples now compile
Parent p;
p = new App().new ChildBuilder()
.withName("Test")
.withNickName("Test1")
.build();
p = new App().new ChildBuilder()
.withNickName("Test1")
.withName("Test")
.build();
}
class Parent {
public Parent(String name) {}
}
class Child extends Parent {
public Child(String name, String nickName) { super(name); }
}
abstract class AbstractParentBuilder<T extends AbstractParentBuilder<T>> {
protected String name;
protected AbstractParentBuilder() {}
public Parent build() { return new Parent(name); }
@SuppressWarnings("unchecked")
public T withName(String name) {
this.name = name;
return (T) this;
}
}
class ParentBuilder extends AbstractParentBuilder<ParentBuilder> {}
abstract class AbstractChildBuilder<T extends AbstractChildBuilder<T>> extends AbstractParentBuilder<T> {
protected String nickName;
protected AbstractChildBuilder() {}
public Child build() { return new Child(name, nickName); }
@SuppressWarnings("unchecked")
public T withNickName(String nickName) {
this.nickName = nickName;
return (T) this;
}
}
class ChildBuilder extends AbstractChildBuilder<ChildBuilder> {}
publicstaticvoidmain(字符串[]args){
//这两个例子现在都可以编译了
亲本p;
p=新应用程序()。新儿童生成器()
.withName(“测试”)
.昵称为(“测试1”)
.build();
p=新应用程序()。新儿童生成器()
.昵称为(“测试1”)
.withName(“测试”)
.build();
}
班级家长{
公共父项(字符串名称){}
}
类子级扩展父级{
公共子项(字符串名称,字符串昵称){super(名称);}
}
抽象类AbstractParentBuilder{
受保护的字符串名称;
受保护的AbstractParentBuilder(){}
公共父级生成(){返回新的父级(名称);}
@抑制警告(“未选中”)
带名称的公共T(字符串名称){
this.name=名称;
返回(T)这个;
}
}
类ParentBuilder扩展了AbstractParentBuilder{}
抽象类AbstractChildBuilder扩展了AbstractParentBuilder{
保护字符串昵称;
受保护的AbstractChildBuilder(){}
public Child build(){返回新的子级(名称、昵称);}
@抑制警告(“未选中”)
带昵称的公共T(字符串昵称){
this.昵称=昵称;
返回(T)这个;
}
}
类ChildBuilder扩展了AbstractChildBuilder{}
(在Java中,您通常会听到它被称为“自我类型模式”。)太棒了,谢谢。你有没有关于我也改变了构造函数的来源,使其不直接依赖于构建器,而是依赖于实际的参数,因为它通常是这样做的?我正在构建我的实现,主要是基于不同的实现。另外,如果我正确地阅读了泛型,那么我们正在配置父构建器,以便在运行时使用子构建器的类型进行实例化?@Chris Uh,我没有这方面的源代码,抱歉,这正是我记得大多数时候看到的(现在看到这条语句,有点像波浪状,抱歉)。在将构建器传递给构造函数时,如果您想选择使用单独接受参数的常规构造函数,则必须单独添加该构造函数。但是,如果需要,可以将Parent
和Child
的参数分别更改为AbstractParentBuilder
和AbstractChildBuilder
。