高效 Java 中的构建器模式

发布于 2024-10-17 10:21:21 字数 1376 浏览 10 评论 0原文

我最近开始阅读 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();
    }
}

我收到以下编译器错误:

包含的封闭实例 effectivejava.BuilderPattern.NutritionalFacts.Builder 是必须的 营养成分 n = 新 NutritionalFacts.Builder(10).carbo(23).fat(1).build();

我不明白该消息的含义。请解释一下。上面的代码与 Bloch 在他的书中建议的示例类似。

I have recently started to read Effective Java by Joshua Bloch. I found the idea of the Builder pattern [Item 2 in the book] really interesting. I tried to implement it in my project but there were compilation errors. Following is in essence what I was trying to do:

The class with multiple attributes and its builder class:

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

Class where I try to use the above class:

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

I am getting the following compiler error:

an enclosing instance that contains
effectivejava.BuilderPattern.NutritionalFacts.Builder
is required
NutritionalFacts n = new
NutritionalFacts.Builder(10).carbo(23).fat(1).build();

I do not understand what the message means. Please explain. The above code is similar to the example suggested by Bloch in his book.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

十秒萌定你 2024-10-24 10:21:21

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

public class NutritionFacts {
    public static class Builder {
    }
}

参考:嵌套类

Make the builder a static class. Then it will work. If it is non-static, it would require an instance of its owning class - and the point is not to have an instance of it, and even to forbid making instances without the builder.

public class NutritionFacts {
    public static class Builder {
    }
}

Reference: Nested classes

娇妻 2024-10-24 10:21:21

您应该将 Builder 类设置为静态,并且还应该将字段设置为最终字段,并使用 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();

You should make the Builder class as static and also you should make the fields final and have getters to get those values. Don't provide setters to those values. In this way your class will be perfectly immutable.

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

And now you can set the properties as follows:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
如梦亦如幻 2024-10-24 10:21:21

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

To generate an inner builder in Intellij IDEA, check out this plugin: https://github.com/analytically/innerbuilder

怪异←思 2024-10-24 10:21:21

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

您提供的示例用法失败,因为不存在 Builder 实例。出于所有实际目的的静态类总是被实例化。如果您不将其设为静态,您需要说:

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

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

You are trying access a non-static class in a static way. Change Builder to static class Builder and it should work.

The example usage you give fails because there is no instance of Builder present. A static class for all practical purposes is always instantiated. If you don't make it static, you'd need to say:

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

Because you would need to construct a new Builder every time.

黯淡〆 2024-10-24 10:21:21

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

请查阅非静态内部类静态内部类

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

You need to declare the Builder inner class as static.

Consult some documentation for both non-static inner classes and static inner classes.

Basically the non-static inner classes instances cannot exist without attached outer class instance.

铜锣湾横着走 2024-10-24 10:21:21

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

@Builder 可让您自动生成使您的类可实例化所需的代码,例如:

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

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

Once you've got an idea, in practice, you may find lombok's @Builder much more convenient.

@Builder lets you automatically produce the code required to have your class be instantiable with code such as:

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

Official documentation: https://www.projectlombok.org/features/Builder

泼猴你往哪里跑 2024-10-24 10:21:21

这意味着您无法创建封闭类型。这意味着首先您必须创建“父”类的实例,然后从该实例您可以创建嵌套类实例。

NutritionalFacts n = new NutritionalFacts()

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

嵌套类

This mean that you cant create enclose type. This mean that first you have to cerate a instance of "parent" class and then from this instance you can create nested class instances.

NutritionalFacts n = new NutritionalFacts()

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

Nested Classes

旧伤慢歌 2024-10-24 10:21:21

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

The Builder class should be static. I don't have time right now to actually test the code beyond that, but if it doesn't work let me know and I'll take another look.

时光清浅 2024-10-24 10:21:21

当你有两个不同的类时,我个人更喜欢使用另一种方法。所以你不需要任何静态类。这基本上是为了避免在必须创建新实例时编写 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();

I personally prefer to use the other approach, when you have 2 different classes. So you don't need any static class. This is basically to avoid write Class.Builder when you has to create a new instance.

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

So, you can use your builder like this:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
看轻我的陪伴 2024-10-24 10:21:21

正如许多人已经指出的那样,您需要使类静态
只是一点小小的补充 - 如果你愿意,有一种没有静态方法的不同方法。

考虑一下这一点。通过在类中声明类似 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 示例。

As many already stated here you need to make the class static.
Just small addition - if you want, there is a bit different way without static one.

Consider this. Implementing a builder by declaring something like withProperty(value) type setters inside the class and make them return a reference to itself. In this approach, you have a single and an elegant class which is a thread safe and concise.

Consider this:

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

Check it out for more Java Builder examples.

再浓的妆也掩不了殇 2024-10-24 10:21:21

您需要将Builder类更改为静态类Builder。然后它就会工作得很好。

You need to change Builder class to static class Builder. Then it will work fine.

那些过往 2024-10-24 10:21:21

其他解决方案将内存分配加倍以实例化对象。下面的解决方案就不存在这个问题。

public class NutritionalFacts{

    private int sodium;
    private int fat;
    private int carbo;

    private NutritionalFacts(){}

    public int getSodium(){ return sodium;}

    public int getFat(){ return fat;}

    public int getCarbo(){ return carbo;}

    public static class Builder{
        private NutritionalFacts nutrionalFacts;

        public Builder(){
           nutrionalFacts = new NutritionalFacts();
        }

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

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

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

        public NutritionalFacts build(){
            return nutrionalFacts;
        }
    }
}

The other solutions double the memory allocation to instantiate the object. The following solution does not have that problem.

public class NutritionalFacts{

    private int sodium;
    private int fat;
    private int carbo;

    private NutritionalFacts(){}

    public int getSodium(){ return sodium;}

    public int getFat(){ return fat;}

    public int getCarbo(){ return carbo;}

    public static class Builder{
        private NutritionalFacts nutrionalFacts;

        public Builder(){
           nutrionalFacts = new NutritionalFacts();
        }

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

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

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

        public NutritionalFacts build(){
            return nutrionalFacts;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文