【问题标题】:java type inference in builder pattern构建器模式中的 java 类型推断
【发布时间】:2017-07-27 23:39:12
【问题描述】:

我想创建一个参数化(通用)类MyType<T> 及其构建器。 builder 将有一些忽略类型 T 的方法和一些使用该类型的方法。但似乎使用这种模式我必须重复 T 声明:

                     how to omit this declaration?
                                   |
                                   V
MyType<String> myType = Builder.<String>of()
                               .method1(argument_can_be_of_any_type)
                               .method2(argument_must_be_of_type_T = String)
                               .build();

是否可以使用一些 java 语法技巧或其他设计模式来省略第二种类型声明并使 API 更加用户友好?比如:

List<String> aList = Lists.emptyList();

List<String> aList = new LinkedList<>();   

我不想在构建器中强制执行任何方法顺序

【问题讨论】:

  • 请发帖Builder班级。
  • method2 的参数是否必须是字符串类型? (换句话说,我们可以从该参数推断出 build() 的返回值的最终类型吗?或者它可以是任何东西吗?
  • @ChrisRaser 是的,method2 的参数必须是字符串。这就是我想要实现的 - 从该参数推断类型 T 而无需显式声明
  • @piotrek,我在下面发布了一个修改后的解决方案(其他人的有用建议)。

标签: java generics design-patterns type-inference builder


【解决方案1】:

由于我的原始答案没有像宣传的那样工作,我已经做了一些修改。 (感谢 shmosel 发现问题,感谢 Daniel Pryden 提出的解决方案。)

/* contents of Box.java */
public class Box<T>
{
    private T contents;
    private Object data;

    protected Box(T contents, Object data) {
        this.contents = contents;
        this.data = data;
    }

    public static BoxBuilder builder() {
        return new BoxBuilder();
    }

    public T getContents() {
        return contents;
    }
}

/* contents of BoxBuilder.java */
public class BoxBuilder
{
    private Object data;

    public BoxBuilder withAnything(Object o) {
        this.data = o;
        return this;
    }

    // Infers new type from argument
    public <T> TypedBoxBuilder<T> withBoxContent(T contents) {
        TypedBoxBuilder<T> builder = new TypedBoxBuilder<T>();
        builder.setData(data);
        builder.setContents(contents);

        return builder;
    }
}

/* contents of TypedBoxBuilder.java */
public class TypedBoxBuilder<T>
{
    private T contents;
    private Object data;

    public TypedBoxBuilder() {
    }

    public TypedBoxBuilder<T> withAnything(Object data) {
        this.data = data;
        return this;
    }

    public TypedBoxBuilder<T> withContents(T contents) {
        this.contents = contents;
        return this;
    }

    public Box<T> build() {
        return new Box<T>(contents, data);
    }

    public void setContents(T contents) {
        this.contents = contents;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

这是客户端代码:

Box<String> box = Box.builder() // Returns BoxBuilder
        .withBoxContent("FOO") // Returns TypedBoxBuilder<String>
        .withAnything(42) // Returns TypedBoxBuilder<String>
        .build(); // Returns Box<String>
String c = box.getContents();

这也有效:

Box<String> box = Box.builder() // Returns BoxBuilder
        .withAnything(42) // Returns BoxBuilder
        .withBoxContent("FOO") // Returns TypedBoxBuilder<String>
        .build(); // Returns Box<String>
String c = box.getContents();

【讨论】:

  • 这是个糟糕的主意。如果您使用的是原始类型,则泛型参数是无用的,更不用说它会在同一语句中删除任何不相关的泛型。你错过了这个method2(argument_must_be_of_type_T)吗?
  • 这太接近了。您需要的是两种构建器类型,BuilderTypedBuilder&lt;T&gt; extends Builder。然后builder() 可以返回Builder(非通用),withBoxContent(T) 可以返回TypedBuilder&lt;T&gt;
  • @shmosel,不错。我没有看到擦除其他泛型的问题。代码编译并正确运行,但是当我看到你的评论时,我检查了你是对的——它不是类型安全的。丹尼尔,我知道你要去哪里,我想,我对它做了一些修改,但它仍然有问题 shmosel 被发现。我希望看到一个很好的方法来做这样的事情(安全地从 Builder 的参数推断构建实例的类型),但我看不到让它工作的方法。
  • @shmosel,丹尼尔,我正在努力赎回自己。我接受了你的两个建议,并把至少像宣传的那样有效的东西放在一起。我仍然不是 100% 相信这是一个好主意,但考虑到有限的背景,我会给 piotrek 一些回旋余地。
  • 努力改进您的答案。现在好像还不错。 +1。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-31
  • 1970-01-01
  • 2018-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多