到底应不应该使用@Builder

大多数同学使用 @Builder 无非就是为了链式编程,然而 @Builder 并不是链式编程的最佳实践,它会额外创建内部类,存在继承关系时还需要使用 @SuperBuilder 注解,设置默认值时也需要额外的 @Builder.Default 去设置默认值,无疑增加了很多不必要的复杂度。

一、案例

@Builder 注解是 Lombok 库中的一个注解,用于简化 Java 对象的构建过程。它通过生成一个构建器模式(Builder Pattern)来创建对象,使得代码更简洁和易于维护。以下是一个使用 @Builder 注解的简单示例:

假设我们有一个 User 类:

import lombok.Builder;
import lombok.ToString;@Builder
@ToString
public class User {private String name;private int age;private String email;
}

在例子中,@Builder 注解会为 User 类生成一个静态内部类 UserBuilder,用于构建 User 对象。@ToString 注解则是为了方便输出对象信息。

使用 @Builder 注解生成的构建器来创建 User 对象:

public class Main {public static void main(String[] args) {User user = User.builder().name("John Doe").age(30).email("johndoe@example.com").build();System.out.println(user);}
}

在这个示例中,通过 User.builder() 方法获取一个 UserBuilder 实例,然后通过链式调用设置各个属性,最后调用 build() 方法创建 User 对象。

这种方式的优点是可以灵活地设置对象的属性,并且不需要创建多个构造函数来满足不同的初始化需求。特别是在属性较多的类中,使用 @Builder 可以显著提高代码的可读性和可维护性。

二、不足之处

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

假设我们有一个 Order 类,其中 orderId 是必须的,而 description 是可选的:

import lombok.Builder;
import lombok.ToString;@Builder
@ToString
public class Order {private String orderId;  // 必须的参数private String description;  // 可选的参数
}

在使用 @Builder 构建 Order 对象时,可能会出现这样的情况:

public class Main {public static void main(String[] args) {// 忘记设置必须的 orderIdOrder order = Order.builder().description("This is an optional description").build();System.out.println(order);}
}

在这个示例中,由于 orderId 是一个必须的参数,但在构建 Order 对象时没有提供 orderId,导致生成的对象可能不合法或不完整。

很多人 喜欢 @Builder 和 @Data 搭配使用,导致生成的构造器是可变的,它允许使用 setter 方法修改构造器的状态。这违反了构造器模式的原则,构造器应该是不可变的,一旦创建就不能被修改。

如果非要使用 @Builder ,那么不要用 @Data ,要用 @Getter。相对来说,反而 @Accessors 的行为更符合这个要求。

@Builder 生成的构造器不适合用于短暂的对象,它会增加代码的复杂度和冗余。构造器模式更适合用于生命周期较长、有多种变体的对象。

实际使用中经常发现 @Builder 滥用的情况,有些仅仅一两个属性的类也都要用 @Builder,真的没必要用,直接用全参的构造方法都比这更简洁。

@Builder 生成的构造器不能处理抽象类型的参数,它只能接受具体类型的对象。这限制了构造器的灵活性和扩展性,不能根据不同的需求创建不同风格的对象。

假设我们有一个抽象的 Vehicle 类和一个具体的 Car 类:

public abstract class Vehicle {private String brand;public Vehicle(String brand) {this.brand = brand;}public String getBrand() {return brand;}
}public class Car extends Vehicle {private int numberOfDoors;public Car(String brand, int numberOfDoors) {super(brand);this.numberOfDoors = numberOfDoors;}public int getNumberOfDoors() {return numberOfDoors;}
}

现在,我们尝试使用 @Builder 来创建一个包含 Vehicle 类型参数的 Garage 类:

import lombok.Builder;
import lombok.ToString;@Builder
@ToString
public class Garage {private Vehicle vehicle;  // 抽象类型参数
}

在使用 @Builder 创建 Garage 对象时,我们会遇到问题,因为 Vehicle 是一个抽象类,无法直接实例化:

public class Main {public static void main(String[] args) {// 这段代码会有问题,因为 Vehicle 是抽象的,不能直接实例化Garage garage = Garage.builder().vehicle(new Vehicle("Generic Brand") {})  // 错误:不能实例化抽象类.build();System.out.println(garage);}
}

解决方案:使用具体类型:在构建器中使用具体类型的对象,而不是抽象类型。例如,直接使用 Car 类

public class Main {public static void main(String[] args) {Car car = new Car("Toyota", 4);Garage garage = Garage.builder().vehicle(car).build();System.out.println(garage);}
}

继承关系时,子类需要使用 @SuperBuilder。对象继承后,子类的 Builder 因为构造函数的问题,使用不当大概率会报错,并且无法设置父类的属性,还需要使用 @SuperBuilder 来解决问题。

假设我们有一个父类 Vehicle 和一个子类 Car,并希望通过构建器来设置它们的属性:通过 @SuperBuilder,我们可以在子类的构建器中设置父类的属性:

public class Main {public static void main(String[] args) {Car car = Car.builder().brand("Toyota")      // 设置父类的属性.model("Corolla")     // 设置父类的属性.numberOfDoors(4)     // 设置子类的属性.build();System.out.println("Brand: " + car.getBrand());System.out.println("Model: " + car.getModel());System.out.println("Number of Doors: " + car.getNumberOfDoors());}
}

@SuperBuilder 注解:它是 @Builder 的增强版本,专门用于处理继承关系。使用 @SuperBuilder 可以在子类的构建器中访问和设置父类的属性。

final 关键字:在这个示例中,属性被声明为 final,确保它们在对象构建后不可变。

构建器链:@SuperBuilder 允许在子类的构建器中调用父类的构建器方法,从而设置父类的属性。

设置默认值需要使用 @Builder.Default。很容易因为对此不了解,导致默认值不符合预期导致出现 BUG。

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;@Getter
@Builder
@ToString
public class User {private final String username;private final String email;@Builder.Defaultprivate final boolean active = true;  // 默认值@Builder.Defaultprivate final int loginAttempts = 0;  // 默认值
}public class Main {public static void main(String[] args) {// 使用构建器创建对象,并未显式设置 active 和 loginAttemptsUser user = User.builder().username("john_doe").email("john.doe@example.com").build();System.out.println(user);// 显式设置 active 和 loginAttemptsUser anotherUser = User.builder().username("jane_doe").email("jane.doe@example.com").active(false).loginAttempts(3).build();System.out.println(anotherUser);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/67533.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软官方Windows 10系统ISO镜像文件下载指南

简介 什么是ISO镜像文件 ISO镜像文件是一种特殊的数字文件格式, 精确复制了物理光盘的所有内容和结构 。这种文件通常用于存储完整的操作系统安装程序或其他大型软件包,便于在网络上传输和长期保存。ISO文件的核心优势在于其高度的完整性和可靠性&…

RabbitMQ-消息可靠性以及延迟消息

目录 消息丢失 一、发送者的可靠性 1.1 生产者重试机制 1.2 生产者确认机制 1.3 实现生产者确认 (1)开启生产者确认 (2)定义ReturnCallback (3)定义ConfirmCallback 二、MQ的持久化 2.1 数据持久…

fgets、scanf存字符串应用

题目1 夺旗(英语:Capture the flag,简称 CTF)在计算机安全中是一种活动,当中会将“旗子”秘密地埋藏于有目的的易受攻击的程序或网站。参赛者从其他参赛者或主办方偷去旗子。 非常崇拜探姬的小学妹最近迷上了 CTF&am…

【C语言系列】深入理解指针(1)

前言 总所周知,C语言中指针部分是非常重要的,这一件我们会介绍指针相关的内容,当然后续我还会出大概4篇与指针相关的文章,来深入的讲解C语言指针部分,希望能够帮助到指针部分薄弱或者根本不会的程序员们,后…

力扣面试150 串联所有单词的子串 分组滑动窗口

Problem: 30. 串联所有单词的子串 参考题解 滑动窗口 class Solution {public List<Integer> findSubstring(String s, String[] words) {int n s.length(), m words.length, w words[0].length();// 统计 words 中「每个目标单词」的出现次数Map<String, Integ…

CSS笔记01

黑马程序员视频地址&#xff1a; 前端Web开发HTML5CSS3移动web视频教程https://www.bilibili.com/video/BV1kM4y127Li?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes 目录 引入方式 CSS特性 继承性 层叠性 优先级 Emmet写法 …

django应急物资管理系统

Django应急物资管理系统是一种高效、智能的管理系统&#xff0c;旨在应对自然灾害、事故灾难等突发事件&#xff0c;确保救援物资能够及时、准确地调配到需要的地方。 一、系统背景与意义 在现代社会&#xff0c;各类突发事件频繁发生&#xff0c;对人民生命财产安全构成严重…

管理口令安全和资源(二)

DBMS_METADATA DBMS_METADATA 是 Oracle 数据库中的一个包&#xff0c;它提供了用于管理数据库元数据的工具和过程。元数据是关于数据的数据&#xff0c;它描述了数据库的结构&#xff0c;包括表、视图、索引、存储过程、用户和其他数据库对象的信息。DBMS_METADATA 包允许用户…

安路FPGA开发工具TD:问题解决办法 及 Tips 总结

安路科技&#xff08;Anlogic&#xff09;是一家专注于高性能、低功耗可编程逻辑器件&#xff08;FPGA&#xff09;设计和生产的公司。其提供的开发工具TD&#xff08;TangDynasty&#xff09;是专门为安路FPGA系列产品设计的集成开发环境&#xff08;IDE&#xff09;。以下是对…

Java常用时间类

JDK7的时间类 1&#xff1a;Date类 2&#xff1a;SimpleDateFormat类 3&#xff1a;Calendar类 JDK8的时间类 1&#xff1a;Zoneld类 2&#xff1a;Instant类 3&#xff1a;ZoneDateTime 4&#xff1a;LocalDate 5&#xff1a;LocalTime 6&#xff1a;LocalDateTime …

模块化架构与微服务架构,哪种更适合桌面软件开发?

前言 在现代软件开发中&#xff0c;架构设计扮演着至关重要的角色。两种常见的架构设计方法是模块化架构与微服务架构。它们各自有独特的优势和适用场景&#xff0c;尤其在C#桌面软件开发领域&#xff0c;模块化架构往往更加具有实践性。本文将对这两种架构进行对比&#xff0…

Java开发提效秘籍:巧用Apache Commons IO工具库

一、引言 在 Java 开发的广袤领域中&#xff0c;输入输出&#xff08;I/O&#xff09;操作宛如一座桥梁&#xff0c;连接着程序与外部世界&#xff0c;从文件的读取与写入&#xff0c;到网络数据的传输&#xff0c;I/O 操作无处不在&#xff0c;其重要性不言而喻。然而&#xf…

使用 Helm 安装 Redis 集群

在 Kubernetes 集群中使用 Helm 安装 Redis 集群可以极大地简化部署和管理 Redis 的过程。本文将详细介绍如何使用 Helm 安装 Redis 集群&#xff0c;并提供一些常见问题的解决方案。 前提条件 Kubernetes 集群。&#xff08;略&#xff09;已安装 Helm 工具。搭建了存储类nf…

算法刷题笔记——图论篇

这里写目录标题 理论基础图的基本概念图的种类度 连通性连通图强连通图连通分量强连通分量 图的构造邻接矩阵邻接表 图的遍历方式 深度优先搜索理论基础dfs 与 bfs 区别dfs 搜索过程深搜三部曲所有可达路径广度优先搜索理论基础广搜的使用场景广搜的过程 岛屿数量孤岛的总面积沉…

C 语言的void*到底是什么?

一、void* 的类型任意性 void* 是一种通用指针类型。它可以指向任意类型的数据。例如&#xff0c;它可以指向一个整数&#xff08;int&#xff09;、一个浮点数&#xff08;float&#xff09;、一个字符&#xff08;char&#xff09;或者一个结构体等。在C语言中&#xff0c;当…

Redis延迟队列详解

以下是对 Redis 延迟队列的详细解释&#xff1a; 一、什么是 Redis 延迟队列 Redis 延迟队列是一种使用 Redis 实现的消息队列&#xff0c;其中的消息在被消费之前会等待一段时间&#xff0c;这段时间就是延迟时间。延迟队列常用于一些需要延迟处理的任务场景&#xff0c;例如订…

利用免费GIS工具箱实现高斯泼溅切片,将 PLY 格式转换为 3dtiles

在地理信息系统&#xff08;GIS&#xff09;和三维数据处理领域&#xff0c;不同数据格式有其独特应用场景与优势。PLY&#xff08;Polygon File Format&#xff09;格式常用于存储多边形网格数据&#xff0c;而 3DTiles 格式在 Web 端三维场景展示等方面表现出色。将 PLY 格式…

【数据分析】02- A/B 测试:玩转假设检验、t 检验与卡方检验

一、背景&#xff1a;当“审判”成为科学 1.1 虚拟场景——法庭审判 想象这样一个场景&#xff1a;有一天&#xff0c;你在王国里担任“首席审判官”。你面前站着一位嫌疑人&#xff0c;有人指控他说“偷了国王珍贵的金冠”。但究竟是他干的&#xff0c;还是他是被冤枉的&…

ZooKeeper 核心知识全解析:架构、角色、节点与应用

1.ZooKeeper 分布式锁怎么实现的 ZooKeeper 是一个高效的分布式协调服务&#xff0c;它提供了简单的原语集来构建更复杂的同步原语和协调数据结构。利用 ZooKeeper 实现分布式锁主要依赖于它的顺序节点&#xff08;Sequential Node&#xff09;特性以及临时节点&#xff08;Ep…

数据结构与算法之递归: LeetCode 47. 全排列 II (Ts, Py, Go版)

全排列 II https://leetcode.cn/problems/permutations-ii/description/ 描述 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列 示例 1 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2 输入&#x…