【Java基础】大家都在使用 @Builder ,我为什么建议你谨慎使用 @Builder

  • hello,大家好,我是你们的老朋友 Lorin,本周在开发中使用 @Builder (@Builder 是一个注解,通常与 Lombok 这种 Java 代码生成工具一起使用,可以帮助简化 Java 类的构建器模式(Builder Pattern)的使用和生成)的时候出现了一个默认值丢失事件,顺便借这个机会研究了一下 @Builder ,特此分享给大家,先说结论:建议在日常开发中谨慎使用 @Builder,为什么呢?废话不多说,发车:

  • 先复现一下我开发中遇到的问题:

@Builder
@Getter
class Student implements {
    private String num;

    private String name;

    private String address = "default";
}

public class TestExample {
    public static void main(String[] args) {
        Student student = Student.builder().name("Student").num("123").build();
        System.out.println(JSON.toJSONString(student));
    }
}

{"name":"Student","num":"123"}

  • 我们会发现我们设置的默认值 address = "default" 丢失了,我大感疑惑,因为我也是第一次遇到,打开编译生成的 class 文件一看豁然开朗:

class Student  {
    private String num;
    private String name;
    private String address = "default";

    Student(String num, String name, String address) {
        this.num = num;
        this.name = name;
        this.address = address;
    }

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

    public String getNum() {
        return this.num;
    }

    public String getName() {
        return this.name;
    }

    public String getAddress() {
        return this.address;
    }

    public static class StudentBuilder {
        private String num;
        private String name;
        private String address;

        StudentBuilder() {
        }

        public StudentBuilder num(String num) {
            this.num = num;
            return this;
        }

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder address(String address) {
            this.address = address;
            return this;
        }

        public Student build() {
            return new Student(this.num, this.name, this.address);
        }

        public String toString() {
            return "Student.StudentBuilder(num=" + this.num + ", name=" + this.name + ", address=" + this.address + ")";
        }
    }
}

  • 知道原因后解决当然很简单,lombok 提供了 @Builder.Default 来设置默认值:

@Builder
@Getter
class Student {
    private String num;

    private String name;

    @Builder.Default
    private String address = "default";
}

public class TestExample {
    public static void main(String[] args) {
        Student student = Student.builder().name("Student").num("123").build();
        System.out.println(JSON.toJSONString(student));
    }
}


{"address":"default","name":"Student","num":"123"}


class Student {
    private String num;
    private String name;
    private String address;

    private static String $default$address() {
        return "default";
    }

    Student(String num, String name, String address) {
        this.num = num;
        this.name = name;
        this.address = address;
    }

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

    public String getNum() {
        return this.num;
    }

    public String getName() {
        return this.name;
    }

    public String getAddress() {
        return this.address;
    }

    public static class StudentBuilder {
        private String num;
        private String name;
        private boolean address$set;
        private String address$value;

        StudentBuilder() {
        }

        public StudentBuilder num(String num) {
            this.num = num;
            return this;
        }

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder address(String address) {
            this.address$value = address;
            this.address$set = true;
            return this;
        }

        public Student build() {
            String address$value = this.address$value;
            
            if (!this.address$set) {
                address$value = Student.$default$address();
            }

            return new Student(this.num, this.name, address$value);
        }

        public String toString() {
            return "Student.StudentBuilder(num=" + this.num + ", name=" + this.name + ", address$value=" + this.address$value + ")";
        }
    }
}

  • 上面的问题只要知道原理就很好的解决了,那我为什么还建议不使用 @Builder 呢?我们都知道 @Builder 可以简化我们代码的生成,让我们轻松的使用构造器。但 @Builder 同样有很多的不足。

@Builder 的不足

  • @Builder 生成的构造器不是完美的,它不能区分哪些参数是必须的,哪些是可选的。如果没有提供必须的参数,构造器可能会创建出不完整或者不合法的对象。

补充一点:很多人 喜欢 @Builder 和 @Data 搭配使用,包括我自己哈哈哈,这样会导致生成的构造器是可变的,这违反了构造器原理的,构造器应该是不可变的。
因此建议  @Builder 使用在一些不可变的对象中。

  • @Builder 生成的构造器不能处理抽象类型的参数,它只能接受具体类型的对象,限制了灵活性和拓展性。

  • 使用不当很容易报错,增加了使用的复杂性。

- 继承关系时,子类需要使用 @SuperBuilder
- 设置默认值需要使用 @Builder.Default

  • 需要额外创建 Builder 对象。

@Builder 适用的场景

  • 从上面我们可以看出,@Builder 不适合使用在短暂对象上,而是应该使用在长期、固定不变的对象上。

@Data + final 实现字段必填

  • 下面是一个简单的示例:

@Data
class Student {
    
     * 设置为 final 构造必填
     */
    private final String num;

    
     * 设置为 final 构造必填
     */
    private final String name;

    private int age;

    private String address = "default";

    Student(String num, String name) {
        this.num = num;
        this.name = name;
    }
}
public class TestExample {
    public static void main(String[] args) {
        Student student = new Student("123", "小明");
        student.setAge(12);
        System.out.println(student);
    }
}

升级版:使用 @Accessors 实现链式构造 + final 实现字段必填

  • 上面的方法我们发现无法实现类似 @Builder 的链式构造,我们可以结合 @Accessors 实现链式构造

  • @Accessors 是 Lombok 提供的一个注解,用于配置生成的 getter 和 setter 方法的样式和命名方式。

@Accessors 的定义

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
    
    boolean fluent() default false;

    
    boolean chain() default false;

    
    
    String[] prefix() default {};
}

使用示例

@Data
@Accessors(fluent = true)
class Student {
    
     * 设置为 final 构造必填
     */
    private final String num;

    
     * 设置为 final 构造必填
     */
    private final String name;

    private int age;

    private String address = "default";

    public Student(String num, String name) {
        this.num = num;
        this.name = name;
    }
}

public class TestExample {
    public static void main(String[] args) {
        Student student = new Student("123", "小明").age(1).address("北京");
        System.out.println(JSON.toJSONString(student));
    }
}

{"address":"北京","age":1,"name":"小明","num":"123"}


@Data
@Accessors(chain = true)
class Student {
    
     * 设置为 final 构造必填
     */
    private final String num;

    
     * 设置为 final 构造必填
     */
    private final String name;

    private int age;

    private String address = "default";

    public Student(String num, String name) {
        this.num = num;
        this.name = name;
    }
}

public class TestExample {
    public static void main(String[] args) {
        Student student = new Student("123", "小明").setAge(1);
        System.out.println(JSON.toJSONString(student));
    }
}

{"address":"default","age":1,"name":"小明","num":"123"}


@Data
@Accessors(chain = true,prefix = "prefix")
class Student {
    
     * 设置为 final 构造必填
     */
    private final String prefixNum;

    
     * 设置为 final 构造必填
     */
    private final String prefixName;

    private int prefixAge;

    private String prefixAddress = "default";

    public Student(String prefixNum, String prefixName) {
        this.prefixNum = prefixNum;
        this.prefixName = prefixName;
    }
}

public class TestExample {
    public static void main(String[] args) {
        Student student = new Student("123", "小明").setAge(1).setAddress("北京");
        System.out.println(JSON.toJSONString(student));
    }
}

{"address":"北京","age":1,"name":"小明","num":"123"}

实现原理

  • 同样我们可以查看编译后的 class 文件看一下背后是如何实现的(以上面的案例三为例):

class Student {
    private final String prefixNum;
    private final String prefixName;
    private int prefixAge;
    private String prefixAddress = "default";

    public Student(String prefixNum, String prefixName) {
        this.prefixNum = prefixNum;
        this.prefixName = prefixName;
    }

    public String getNum() {
        return this.prefixNum;
    }

    public String getName() {
        return this.prefixName;
    }

    public int getAge() {
        return this.prefixAge;
    }

    public String getAddress() {
        return this.prefixAddress;
    }

    public Student setAge(int prefixAge) {
        this.prefixAge = prefixAge;
        return this;
    }

    public Student setAddress(String prefixAddress) {
        this.prefixAddress = prefixAddress;
        return this;
    }
    
}

  • @Builder 是一个好用的工具,但是我们不能滥用。在构建一些长期、固定不可变的对象时我们可以适当使用 @Builder 进行构建;当构建一些短暂存活的对象时我们可以尝试 使用 @Accessors 实现链式构造 + final 实现字段必填 的方式。

  • 也许大多数朋友其实在日常开发中都是 @Builder 和 @Data 一把梭(包括我自己),但只有不断尝试、总结、尝试改变,才能成为更优秀的自己,水滴总有汇聚成大海的一天。

  • 如果文章对你有帮助,请点赞、收藏、关注、谢谢大家。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
GPT-Neo是一个基于Python的深度学习模型,使用Java调用GPT-Neo需要先将Java与Python集成,并使用Java调用Python代码。这里介绍一种使用Java调用Python的方法,但需要注意的是,这种方法需要在Java和Python之间建立一个通信管道,效率可能不如直接使用Python。 1. 安装Python和GPT-Neo模型,可以参考上面的回答进行安装。 2. 在Python中创建一个简单的HTTP服务,用于接收Java程序发送的请求并调用GPT-Neo模型生成文本。可以使用Python的Flask框架来创建HTTP服务,例如: ``` from flask import Flask, request import gpt_neo app = Flask(__name__) model = gpt_neo.load_model('1.3B') @app.route('/generate', methods=['POST']) def generate(): prompt = request.form['prompt'] length = request.form.get('length', 50) temperature = request.form.get('temperature', 0.7) output = model.generate(prompt, length=length, temperature=temperature) return output if __name__ == '__main__': app.run() ``` 这里创建了一个名为generate的接口,接收一个名为prompt的参数作为生成文本的输入,并可选地接收length和temperature参数。接收到请求后,调用GPT-Neo模型生成文本并返回。 3. 在Java使用HTTP客户端库发送请求并接收响应。可以使用Java的OkHttp库,例如: ``` import okhttp3.*; public class GPTNeoClient { private final OkHttpClient httpClient = new OkHttpClient(); public String generate(String prompt, int length, double temperature) throws IOException { MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded"); RequestBody body = new FormBody.Builder() .add("prompt", prompt) .add("length", String.valueOf(length)) .add("temperature", String.valueOf(temperature)) .build(); Request request = new Request.Builder() .url("http://localhost:5000/generate") .post(body) .build(); Response response = httpClient.newCall(request).execute(); return response.body().string(); } } ``` 这里封装了一个GPTNeoClient类,提供一个generate方法用于发送请求并返回生成的文本。生成文本的输入作为请求参数发送到Python的HTTP服务中,等待Python处理并返回响应。 需要注意的是,这种方法需要在Java和Python之间建立通信管道,效率可能不如直接使用Python。同时,使用GPT-Neo生成文本时需要谨慎,避免生成不当的内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术小羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值