【问题标题】:Java Builder with inheritance - type issue具有继承的 Java Builder - 类型问题
【发布时间】: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 具有由边界定义的泛型类型。它们不能被定义为具体类型,因为这会阻止静态函数在派生类型中被覆盖——而泛型边界可以在子类中收紧。有没有更好的方法来获取构建器实例,而无需调用者指定此时应该隐式的泛型类型?

CBuilder 定义同样是有界的,而不是绝对的,因为预计派生类(例如 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


【解决方案1】:

如果您尝试使用super 而不是extends,您的初始示例将编译:

public static Builder<? super P, ? extends Builder> builder() {
    return new Builder<>();
}

这是我拥有的完整示例:

public class P {
    public P() {
    }

    protected P(Builder<? extends P, ? extends Builder> builder) {
    }

    public static Builder<? super P, ? extends Builder> builder() {
        return new Builder<>();
    }

    public static void main(String[] args) {
        P p0 = new P();
        P p1 = P.builder()
                .set(p0)        // no error!
                .build();
    }

    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);
        }
    }
}

它不编译的原因是你可以通过extends读取子类型,但只能通过super写入子类型。

尽管这个页面是关于 Kotlin 类型的,但它对 Java 通配符类型参数也很有启发性:

https://kotlinlang.org/docs/reference/generics.html

描述了extends在Java中的典型行为:

通配符类型参数 ? extends E 表示这个方法 接受 E 的对象集合或 E 的某些子类型,而不仅仅是 E 本身。这意味着我们可以安全地读取项目中的 E (这个集合的元素是 E 的子类的实例),但是 无法写入,因为我们不知道哪些对象符合 E的未知亚型。

顺便说一句:为了让你的 Q 类为我编译,我需要将它重写为:

public class Q
        extends P {

    protected Q(Builder<? extends Q, ? extends Builder> builder) {
        super(builder);
    }

    public static class Builder<C extends Q, B extends Builder<C, B>>
            extends P.Builder<C, B> {

        @Override
        public B set(C instance) {
            super.set(instance);
            return (B) this;  // unchecked cast
        }


        @Override
        public C build() {
            return (C) new Q(this); // unchecked cast
        }
    }
}

Q 类中的builder 方法不能有override 注释。不过有两个未经检查的演员表。

【讨论】:

  • 不幸的是,在静态函数P.builder() 中将extends 更改为super 会阻止Q.builder() 的相应定义。
猜你喜欢
  • 2013-06-14
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多