原地址:https://www.jianshu.com/p/0d8fc3df3647?from=timeline&isappinstalled=0

很久之前,我在《effective java》上看过Builder构建器相关的内容,但实际开发中不经常用。后来,在项目中使用了lombok,发现它有一个注解“@Builder”,就是为java bean生成一个构建器。于是,回头重新复习了下相关知识,整理如下。

1. lombok使用样例

  1. // 创建名为Officer的java bean
  2. @Builder
  3. public class Officer {
  4. private final String id;
  5. private final String name;
  6. private final int age;
  7. private final String department;
  8. }
  9.  
  10. // 调用构建器生成Officer实例
  11. class BuilderTest {
  12. public static void main(String[] args) {
  13. Officer officer = Officer.builder().id("00001").name("simon qi")
  14. .age(26).department("departmentA").build();
  15. }
  16. }

2. 反编译lombok生成的Officer.class

注意:下面请区分两组名词:"builder方法"和“build方法”,“构造器”和“构建器”。

  1. public class Officer {
  2. private final String id;
  3. private final String name;
  4. private final int age;
  5. private final String department;
  6.  
  7. Officer(String id, String name, int age, String department) {
  8. this.id = id;
  9. this.name = name;
  10. this.age = age;
  11. this.department = department;
  12. }
  13.  
  14. public static Officer.OfficerBuilder builder() {
  15. return new Officer.OfficerBuilder();
  16. }
  17.  
  18. public static class OfficerBuilder {
  19. private String id;
  20. private String name;
  21. private int age;
  22. private String department;
  23.  
  24. OfficerBuilder() {
  25. }
  26.  
  27. public Officer.OfficerBuilder id(String id) {
  28. this.id = id;
  29. return this;
  30. }
  31.  
  32. public Officer.OfficerBuilder name(String name) {
  33. this.name = name;
  34. return this;
  35. }
  36.  
  37. public Officer.OfficerBuilder age(int age) {
  38. this.age = age;
  39. return this;
  40. }
  41.  
  42. public Officer.OfficerBuilder department(String department) {
  43. this.department = department;
  44. return this;
  45. }
  46.  
  47. public Officer build() {
  48. return new Officer(this.id, this.name, this.age, this.department);
  49. }
  50.  
  51. public String toString() {
  52. return "Officer.OfficerBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", department=" + this.department + ")";
  53. }
  54. }
  55. }

我们通过反编译Officer.class,获得上方的源码(最好用idea自带的反编译器,jd-gui反编译的源码不全)。

我们发现源码中有一个OfficerBuilder的静态内部类,我们在调用builder方法时,实际返回了这个静态内部类的实例。这个OfficerBuilder类,具有和Officer相同的成员变量,且拥有名为id,name,age和department的方法。这些以Officer的成员变量命名的方法,都是给OfficerBuilder的成员变量赋值,并返回this。

这些方法返回this,其实就是返回调用这些方法的OfficerBuilder对象,也可称为“返回对象本身”。通过返回对象本身,形成了方法的链式调用。

再看build方法,它是OfficerBuilder类的方法。它创建了一个新的Officer对象,并将自身的成员变量值,传给了Officer的成员变量。所以Officer officer = Officer.builder().id("00001").name("simon qi").age(26).department("departmentA").build();的写法,等价于下面的写法:

  1. Officer.OfficerBuilder officerBuilder = new Officer.OfficerBuilder();
  2. officerBuilder.id("00001").name("simon qi").age(26).department("departmentA");
  3. Officer officer = officerBuilder.build();

所以为什么这种模式叫“构建器”,因为要创建Officer类的实例,首先要创建OfficerBuilder类的实例。而这个OfficerBuilder也就是构建器,是创建Officer对象的一个过渡者。所以利用这种模式,会有中间实例的创建,会加大虚拟机内存的消耗。

3. 只用@Builder注解的bug

我们只用@Builder注解,我发现lombok为Officer类生成的构造器是“default”的(不添加权限修饰符,默认为“default”的)。

我们之所以用构建器模式,是希望用户用构建器提供的方法去创建实例。但“default”的构造器,可以被同package的类调用(default限制不同package类的调用)。所以,我们需要将此构造器设为private的。这时就需要用到“@AllArgsConstructor(access = AccessLevel.PRIVATE)”。我们这时再看反编译后的构造器:

  1. private Officer(String id, String name, int age, String department) {
  2. this.id = id;
  3. this.name = name;
  4. this.age = age;
  5. this.department = department;
  6. }

所以,使用lombok的构建器,应将“@Builder”和“@AllArgsConstructor(access = AccessLevel.PRIVATE)”相结合,最终写法:

  1. @Builder
  2. @AllArgsConstructor(access = AccessLevel.PRIVATE)
  3. public class Officer {
  4. private final String id;
  5. private final String name;
  6. private final int age;
  7. private final String department;
  8. }

4. 为什么使用构建器模式

若一个类具有大量的成员变量,我们就需要提供一个全参的构造器或大量的set方法。这让实例的创建和赋值,变得很麻烦,且不直观。我们通过构建器,可以让变量的赋值变成链式调用,而且调用的方法名对应着成员变量的名称。让对象的创建和赋值都变得很简洁、直观。

5. 链式方法赋值,一定要用构建器模式吗?

不一定要用到构建器模式,之所以使用构建器模式,是因为我们要创造的对象是一个成员变量不可变的对象

你返回去看Officer类和OfficerBuilder类,你会发现Officer类的成员变量都是final的,而OfficerBuilder的成员变量却没用final修饰。因为final修饰的成员变量,需要在实例创建时就把值确定下来。但在类具有大量成员变量的时候,我们是不希望用户直接调用全参构造器的。

所以我们使用了OfficerBuilder的中间类。这个类为了实现链式赋值,才将变量设为非final的。无论你OfficerBuilder实例怎么赋值,怎么改变,当你调用build方法时,就会返回一个成员变量不可变的Officer实例。

那如果有大量属性,但不需要它是成员变量不可变的对象,我们还需要构建器模式吗?答案是,不需要,我们可以参考构建器,把代码赋值改成链式的即可:

  1. public class Officer {
  2. private String id;
  3. private String name;
  4. private int age;
  5. private String department;
  6.  
  7. public static Officer build() {
  8. return new Officer();
  9. }
  10.  
  11. private Officer() {
  12. }
  13.  
  14. public Officer id(String id) {
  15. this.id = id;
  16. return this;
  17. }
  18.  
  19. public Officer name(String name) {
  20. this.name = name;
  21. return this;
  22. }
  23.  
  24. public Officer age(int age) {
  25. this.age = age;
  26. return this;
  27. }
  28.  
  29. public Officer department(String department) {
  30. this.department = department;
  31. return this;
  32. }
  33. }
  34.  
  35. 调用样式:
  36. Officer officer = Officer.build().id("00001").name("simon qi").age(26).department("departmentA");
  37. 其实这时候构造器设为非private也行,写成private,只是为了调用build()显得更好看。
  38. 将构造器设为非private,可以写为如下形式:
  39. Officer officer = new Officer().id("00001").name("simon qi").age(26).department("departmentA");

所以,我觉得你在使用lombok的"@Builder"注解的时候,还是要思考一下。当你不需要成员变量不可变的时候,你完全没必要使用构建器模式,因为这会消耗java虚拟机的内存。

6. 总结

所以,我一直推荐学习知识,要在项目中去学习。通过项目,去探索项目以外的知识点,才是提升自己的快捷方法。而且知识不能学死了,不能网上有哪些知识点,我们就只考虑这些知识点。我们要去思考一些别人不常想到的问题。比如,我们为什么要用中间类去做过渡,这么写的目的是什么。

将上述知识吃透,面试应对构建器的时候,也就得心应手了。而且通过实战去回答问题,也更能彰显你是个爱思考的员工。

【转】通过lombok带你读透Builder构建器的更多相关文章

  1. Java设计模式:Builder(构建器)模式

    概念定义 Builder模式是一步一步创建一个复杂对象的创建型模式.该模式将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来. 应用场景 对象创建过程比较复杂,或对创建顺序或组合有依 ...

  2. 构造器参数过多时考虑使用构建器(Builder)

    一.静态工厂和构造器的局限性 面对需要大量可选参数才能构建对象时,静态工厂和构造器并不能随着可选参数的增加而合理扩展. 假设创建一个类Person需要使用大量的可选参数,其中两个参数是必填的,剩下的都 ...

  3. 少啰嗦!一分钟带你读懂Java的NIO和经典IO的区别

    1.引言 很多初涉网络编程的程序员,在研究Java NIO(即异步IO)和经典IO(也就是常说的阻塞式IO)的API时,很快就会发现一个问题:我什么时候应该使用经典IO,什么时候应该使用NIO? 在本 ...

  4. 漫谈grpc 3:从实践到原理,带你参透 gRPC

    ​ 原文链接:万字长文 | 从实践到原理,带你参透 gRPC 大家好,我是煎鱼. gRPC 在 Go 语言中大放异彩,越来越多的小伙伴在使用,最近也在公司安利了一波,希望这一篇文章能带你一览 gRPC ...

  5. 一文带你读懂什么是vxlan网络

    一个执着于技术的公众号 一.背景 随着云计算.虚拟化相关技术的发展,传统网络无法满足大规模.灵活性要求高的云数据中心的要求,于是便有了overlay网络的概念.overlay网络中被广泛应用的就是vx ...

  6. 实战 | 一文带你读懂Nginx反向代理

    一个执着于技术的公众号 前言 在前面的章节中,我们已经学习了nginx基础知识: 给小白的 Nginx 10分钟入门指南 Nginx编译安装及常用命令 完全卸载nginx的详细步骤 Nginx 配置文 ...

  7. 在 Xamarin.Android 中使用 Notification.Builder 构建通知

    0 背景 在 Android 4.0 以后,系统支持一种更先进的 Notification.Builder 类来发送通知.但 Xamarin 文档含糊其辞,多方搜索无果,遂决定自己摸索. 之前的代码: ...

  8. Flash Builder 调试器无法连接到正在运行的应用程序(57%)

    Flash Builder 调试器无法连接到正在运行的应用程序(57%),可能原因:     1,flashplayer不是debug版.     2,调试器(用debug版flashplayer随便 ...

  9. 设计模式---对象创建模式之构建器模式(Builder)

    一:概念 Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种.Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象 ...

随机推荐

  1. (转载)PIM-SM协议初探(一)路由角色选举

    PIM是Protocol Independent Multicast(协议无关组播)的简称,表示可以利用静态路由或者任意单播路由协议(包括RIP.OSPF.IS-IS.BGP等)所生成的单播路由表为I ...

  2. VMware虚拟机桥接方式与真实主机共享上网

    原始出处 .http://meiling.blog.51cto.com/6220221/1367695 一.先介绍一下VMware网络设置的三种方式: VMWare提供了三种工作模式,host-onl ...

  3. UUID相同导致的网络连接问题

    目录 场景 思路 解决过程 提升虚拟机配置 直连交换机 最终解决方案 总结 场景 有同事从公司寄了一台服务器到现场,用来安装数据库.缓存等组件供开发使用.到了之后,连接电源.网线,设置IP,用vSph ...

  4. Leetcode之动态规划(DP)专题-198. 打家劫舍(House Robber)

    Leetcode之动态规划(DP)专题-198. 打家劫舍(House Robber) 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互 ...

  5. DAY 吐

    今天所学: 一,Linux的文件和目录管理 #1 cd( 变更用户所在目录)直接运行cd会进入root的/root下,后面跟目录名,会进入指定目录下( 后面只能是目录名,不能跟文件名). #2 pwd ...

  6. Vuecli 3.0 项目自定义添加静态目录,支持在index.html引入

    参考链接:https://blog.csdn.net/qq_15253407/article/details/89491255

  7. android简易计算器

    activity_main.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&q ...

  8. Gym - 100989 L / M 【dfs / dp】

    题目链接:http://codeforces.com/gym/100989/problem/L / http://codeforces.com/gym/100989/problem/M 题目大意:给定 ...

  9. vultr 更换服务器

    今天打算去p站看看电影 结果发现自己的vps被封了......记录一下换服务器的过程 首先去 https://www.17ce.com/ ping一下,发现只有国外的服务器能ping通 果然是被封了. ...

  10. [bzoj4842][bzoj1283][Neerc2016]Delight for a Cat/序列_线性规划_费用流

    4842: [Neerc2016]Delight for a Cat_1283: 序列 题目大意:ls是一个特别堕落的小朋友,对于n个连续的小时,他将要么睡觉要么打隔膜,一个小时内他不能既睡觉也打隔膜 ...