Java泛型 + Builder模式

47

我该如何调用下面的start()方法?

package com.example.test;

class Bar {}

public class Foo<K>
{
    final private int count;
    final private K key;

    Foo(Builder<K> b)
    {
        this.count = b.count;
        this.key = b.key;
    }

    public static class Builder<K2>
    {
        int count;
        K2 key;

        private Builder() {}
        static public <K3> Builder<K3> start() { return new Builder<K3>(); }
        public Builder<K2> setCount(int count) { this.count = count; return this; }
        public Builder<K2> setKey(K2 key) { this.key = key; return this; }
        public Foo<K2> build() { return new Foo(this); }
    }

    public static void main(String[] args)
    {
        Bar bar = new Bar();
        Foo<Bar> foo1 = Foo.Builder.start().setCount(1).setKey(bar).build();
        // Type mismatch: cannot convert from Foo<Object> to Foo<Bar>

        Foo<Bar> foo2 = Foo.Builder<Bar>.start().setCount(1).setKey(bar).build();
        // Multiple markers at this line
        // - Bar cannot be resolved
        // - Foo.Builder cannot be resolved
        // - Syntax error on token ".", delete this token
        // - The method start() is undefined for the type Foo<K>
        // - Duplicate local variable fooType mismatch: cannot convert from Foo<Object> to Foo<Bar>

        Foo<Bar> foo3 = Foo<Bar>.Builder.start().setCount(1).setKey(bar).build();
        // Multiple markers at this line
        // - Foo cannot be resolved
        // - Syntax error on token ".", delete this token
        // - Bar cannot be resolved     
    }
}
4个回答

48
你已经接近了:
Foo.Builder.<Bar> start().setCount(1).setKey(bar).build();

干杯!:)

P.S. 如果编译器无法自行推断方法的类型参数,您可以通过调用 obj.<Type> method(...) 强制指定。

P.P.S 您可能想使用:

public Foo<K2> build() {
    return new Foo<K2>(this);
}
避免使用原始类型。

3
每当你在方法中写下一个显式类型参数,上帝就会杀死一只小猫咪 ;) - sfussenegger
2
@Jason:在start()方法中提供密钥并将其传递给私有构造函数,这样你就可以得到像下面这样的内容: Bar bar = new Bar(); Foo.Builder.start(bar); // 类型将从bar中推断出来 - perp
@Andrei不喜欢并不能证明杀小猫是合理的,没有任何理由可以证明!;) - sfussenegger
2
@Jason 我认为那句关于小猫的引用最初来自Josh Bloch。不过我自己也会用到它,至少偶尔会这样。如果你只是自己使用这个构建器,那就直接用吧。但是,如果还有其他开发人员,我的答案可能更容易让他们理解。显式类型参数并不美观,更重要的是,并不是所有开发人员都知道它们的存在。就像你一样,你必须来这里才能让它们正常工作。同样地,其他开发人员将难以使用你的库,因为它们使用了这些参数。所以最终,这归结为可用性和口味问题。 - sfussenegger
1
好的,所以这不是丑陋/混乱的问题,而是语法晦涩/令人困惑的问题。谢谢! - Jason S
显示剩余4条评论

33
如果您是 lombok 项目的粉丝,并使用其注释 @Builder 实现构建器模式,则可以删除所有不必要的代码,就像您正在编写并以更少的代码完成它一样。
示例:
@Builder
public class ErrorResponse<T> {
    private String message;
    private List<String> reasons;
    private List<String> details;
    private T data;
}

您可以像这样初始化它:

ErrorResponse<MyData> myDataError = ErrorResponse.<MyData>builder()
.message("My message")
.build();

29

Andrei 的方法没问题,但大多数程序员可能会难以理解这个较为陌生的语法。使用下面的方式可能更容易:

static public <K3> Builder<K3> start(Class<K3> cls) { return new Builder<K3>(); }

Foo<Bar> foo1 = Foo.Builder.start(Bar.class).setCount(1).setKey(bar).build();

这个类只是用来帮助泛型类型的。虽然不太美观,但至少语法是常识。

另一个选项是直接使用泛型类型的对象开始:

Foo<Bar> foo1 = Foo.Builder.startWithKey(bar).setCount(1).build();

3
这是我的实现方法:
package odmor2018.krit.rtti.builderpattern;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class Person {

    private String firstName;
    private String middleName;
    private String lastName;
    private boolean sex;

    public Person(String firstName, String middleName, String lastName, boolean sex) {
        this.firstName = firstName;
        this.middleName = middleName;
        this.lastName = lastName;
        this.sex = sex;
    }

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" + "firstName=" + firstName + ", middleName=" + middleName + ", lastName=" + lastName + ", sex=" + sex + '}';
    }

    public static class Builder {

        private final Field[] fields = Person.class.getDeclaredFields();
        private final List<Field> fieldsList = Arrays.asList(fields);
        private final List<String> fNames = fieldsList.stream().map(f -> f.getName()).collect(Collectors.toList());

        private final Person nP = new Person();

        public Builder with(String fName, Object value) {
            if (fNames.contains(fName)) {
                int fInd = fNames.indexOf(fName);
                try {
                    Field f = fields[fInd];
                    f.setAccessible(true);
                    f.set(nP, value);
                } catch (Exception ex) {
                    Logger.getLogger(Person.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return this;
        }

        public Person createPerson2() {
            return nP;
        }
    }

    public static void main(String[] args) {
        Person p3 = new Person.Builder()
                .with("firstName", "doooobri2")
                .with("sex", false)
                .createPerson2();
        System.err.println("p3:" + p3);
    }
}

1
好的,明白了,你可以使用反射来实现动态构建器!请注意,你可以使用Lombok来简化这个过程。https://projectlombok.org/features/Builder - gkatzioura

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接