具有继承的Java Builder - 类型问题

时间:2017-10-23 14:36:28

标签: java generics inheritance

我正在尝试定义一个支持继承的构建器实现,并为客户端代码提供一个干净的接口。但是我在set()方法上遇到了“不兼容的类型”问题。此方法允许将buider的状态设置为引用实例的状态。

以下是演示错误的最小示例:

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()具有由边界定义的泛型类型。它们不能被定义为具体类型,因为这会阻止静态函数在派生类型中被覆盖 - 而泛型边界可以在子类中被收紧。有没有更好的方法来获取构建器实例而不需要调用者指定那时应该隐含的泛型类型?

Builder的{​​{1}}定义同样是有界的而不是绝对的,因为预期派生类(比如C)也将从基类的构建器派生出来:

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()方法的参数被定义为正在构建的显式类而不是泛型类型{{ 1}}。

我定义了一个定义通用set()的Builder接口;最初这只是为了强化一致性。但是现在似乎有必要在派生类中为显式类型的C方法提供一些覆盖。仍然需要查看这是否是真正的覆盖,或者是否允许set(C)拨打set() (切片!)

1 个答案:

答案 0 :(得分: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

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

  

通配符类型参数? 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
        }
    }
}

班级builder中的Q方法无法使用override注释。虽然有两个未经检查的演员阵容。