zoukankan      html  css  js  c++  java
  • 【转】通过lombok带你读透Builder构建器

    原地址: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. 总结

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

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

  • 相关阅读:
    maven学习(十六)——使用Maven构建多模块项目
    jsp页面提示“Multiple annotations found at this line:
    SQL中Truncate语法
    java时间工具类,时间相互转换
    Date转换为LocalDateTime
    新建的maven项目里没有src
    maven-version
    spring-boot-configuration-processor
    python爬取文件时,内容为空
    IntelliJ IDEA 创建的文件自动生成 Author 注释 签名
  • 原文地址:https://www.cnblogs.com/gwyy/p/11203080.html
Copyright © 2011-2022 走看看