140

我最近开始阅读 Joshua Bloch 的 Effective Java。我发现 Builder 模式的想法 [书中的第 2 项] 非常有趣。我试图在我的项目中实现它,但出现编译错误。以下本质上是我试图做的事情:

具有多个属性的类及其构建器类:

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

我尝试使用上述类的类:

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

我收到以下编译器错误:

需要包含有效java.BuilderPattern.NutritionalFacts.Builder 的封闭实例 NutritionalFacts n = new NutritionalFacts.Builder(10).carbo(23).fat(1).build();

我不明白消息的含义。请解释。上面的代码类似于 Bloch 在他的书中提出的例子。

4

11 回答 11

178

使构建器成为一个static类。然后它将起作用。如果它是非静态的,它将需要其所属类的实例 - 关键是不要拥有它的实例,甚至禁止在没有构建器的情况下创建实例。

public class NutritionFacts {
    public static class Builder {
    }
}

参考:嵌套类

于 2011-02-15T17:54:20.893 回答
28

您应该将 Builder 类设为静态,并且您应该将字段设为 final 并使用 getter 来获取这些值。不要为这些值提供设置器。这样,您的类将是完全不可变的。

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

现在您可以按如下方式设置属性:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
于 2015-07-31T19:44:42.027 回答
14

要在 Intellij IDEA 中生成内部构建器,请查看此插件:https ://github.com/analytically/innerbuilder

于 2014-01-31T10:09:44.890 回答
12

您正在尝试以静态方式访问非静态类。更改Builderstatic class Builder它应该可以工作。

您给出的示例用法失败,因为没有存在的实例Builder。用于所有实际目的的静态类总是被实例化。如果你不让它静态,你需要说:

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

Builder因为您每次都需要构建一个新的。

于 2011-02-15T17:54:25.417 回答
8

您需要将Builder内部类声明为static.

查阅一些关于非静态内部类静态内部类的文档。

基本上,如果没有附加的外部类实例,非静态内部类实例就不能存在。

于 2011-02-15T17:53:29.137 回答
5

一旦你有了一个想法,在实践中,你可能会发现 lombok@Builder更方便。

@Builder允许您自动生成使您的类可以使用以下代码实例化所需的代码:

Person.builder()
  .name("Adam Savage")
  .city("San Francisco")
  .job("Mythbusters")
  .job("Unchained Reaction")
 .build(); 

官方文档:https ://www.projectlombok.org/features/Builder

于 2018-11-10T11:00:28.397 回答
4

这意味着您不能创建封闭类型。这意味着首先你必须创建一个“父”类的实例,然后从这个实例中你可以创建嵌套的类实例。

NutritionalFacts n = new NutritionalFacts()

Builder b = new n.Builder(10).carbo(23).fat(1).build();

嵌套类

于 2011-02-15T17:53:19.273 回答
3

Builder 类应该是静态的。我现在没有时间实际测试除此之外的代码,但如果它不起作用,请告诉我,我会再看看。

于 2011-02-15T17:53:29.067 回答
0

当您有 2 个不同的类时,我个人更喜欢使用另一种方法。所以你不需要任何静态类。这基本上是为了避免Class.Builder在必须创建新实例时写入。

public class Person {
    private String attr1;
    private String attr2;
    private String attr3;

    // package access
    Person(PersonBuilder builder) {
        this.attr1 = builder.getAttr1();
        // ...
    }

    // ...
    // getters and setters 
}

public class PersonBuilder (
    private String attr1;
    private String attr2;
    private String attr3;

    // constructor with required attribute
    public PersonBuilder(String attr1) {
        this.attr1 = attr1;
    }

    public PersonBuilder setAttr2(String attr2) {
        this.attr2 = attr2;
        return this;
    }

    public PersonBuilder setAttr3(String attr3) {
        this.attr3 = attr3;
        return this;
    }

    public Person build() {
        return new Person(this);
    }
    // ....
}

因此,您可以像这样使用您的构建器:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
于 2018-05-17T08:52:41.310 回答
0

正如许多人在这里已经说过的那样,您需要上课static。只是一个小小的补充——如果你愿意,没有静态的有一点不同的方式。

考虑一下。通过在类中声明类型设置器之类的东西来实现构建器withProperty(value),并使它们返回对自身的引用。在这种方法中,您有一个单一且优雅的类,它是线程安全且简洁的。

考虑一下:

public class DataObject {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first = first; 
    }

    ... 

    public DataObject withFirst(String first){
       this.first = first;
       return this; 
    }

    public DataObject withSecond(String second){
       this.second = second;
       return this; 
    }

    public DataObject withThird(String third){
       this.third = third;
       return this; 
    }
}


DataObject dataObject = new DataObject()
     .withFirst("first data")
     .withSecond("second data")
     .withThird("third data");

查看更多Java Builder示例。

于 2019-08-08T19:03:08.527 回答
0

您需要将Builder类更改为静态类 Builder。然后它会正常工作。

于 2019-08-11T18:30:31.427 回答