【发布时间】:2017-10-30 19:58:18
【问题描述】:
我正在尝试定义一个支持继承的构建器实现,并为客户端代码提供一个干净的接口。但是我在set() 方法上遇到了“不兼容的类型”问题。该方法允许将构建器的状态设置为引用实例的状态。
以下是一个演示错误的最小示例:
public class P
{
public P () {}
protected P (Builder<? extends P,? extends Builder> builder) {}
public static Builder<? extends P,? extends Builder> builder ()
{
return new Builder<> ();
}
public static class Builder<C extends P, B extends Builder<C,B>>
{
public B set (C c) { return (B) this ; }
public C build () { return (C) new P (this) ; }
}
public static void main (String[] args)
{
P p0 = new A ();
P p1 = A.builder ()
.set (p0) // error!
.build ();
}
}
类P 作为“buildee”定义了一个静态内部Builder 类,通过builder() 工厂方法获得。由于我们必须支持派生类,Builder 的定义使其在派生下保持流畅,builder() 使用绑定泛型定义以允许它在派生类中被覆盖。
使用set()的错误是
错误:不兼容的类型:
P无法转换为CAP#1.set (p0)其中CAP#1是一个新的类型变量:CAP#1 extends P来自? extends P的捕获
现在,builder() 定义的 Builder 具有由边界定义的泛型类型。它们不能被定义为具体类型,因为这会阻止静态函数在派生类型中被覆盖——而泛型边界可以在子类中收紧。有没有更好的方法来获取构建器实例,而无需调用者指定此时应该隐式的泛型类型?
C 的 Builder 定义同样是有界的,而不是绝对的,因为预计派生类(例如 Q)也将从基类的构建器派生:
public class Q
extends P
{
protected Q (Builder<? extends Q, ? extends Builder> builder)
{
super (builder);
}
public Builder<? extends Q, ? extends Builder> builder ()
{
return new Builder<> ();
}
public static class Builder<C extends Q, B extends Buider<C,B>>
extends P.Builder<C,B>
{
@Override
public B set (C instance)
{
super.set (instance);
return (B) this;
}
@Override
public C build ()
{
return new Q (this);
}
}
}
现在,这几乎都可以正常工作——以及字段和流利的设置器(未在最小代码中显示)。至少看起来...只有set() 方法会导致问题。
那么是否有set() 和/或builder()(或其他部分)的定义,它将满足编译器并支持继承?
补充一点,在尝试了各种组合之后,唯一能按预期编译和运行的模式是,如果构建器的 set() 方法的参数被定义为正在构建的显式类而不是泛型类型 C。
我定义了一个 Builder 接口,它定义了通用的set(C);最初这只是为了强制一致性。然而,现在似乎有必要在派生类中为显式类型的set() 方法提供一些覆盖。这是否是真正的覆盖,或者是否允许P.builder().set(q) 调用Q::Builder::set(Q),还有待观察(切片!)。
【问题讨论】:
-
如果您将
P组合成Q并编写P的构建器以使用Q的构建器,那么所有这些问题都会消失。虽然为抽象P使用可遗传的构建器代码很常见,但为具体子类维护构建器链听起来像是要避免的事情。 -
@scottb 不幸的是,具体类链已经存在,构造函数的参数数量不断增加......
-
我在一年前用泛型构建器和继承进行了测试,并创建了这个github repo,它还包含一个通过泛型定义返回类型的进一步分支。一些 IDE 在分析代码时遇到了一些困难,尽管在控制台上代码可以编译并执行。
标签: java generics inheritance