zoukankan      html  css  js  c++  java
  • Lombok之@Builder注解

    Lombok之@Builder注解

    前言

    Lombok大家都知道,在使用POJO过程中,它给我们带来了很多便利,省下大量写getset方法、构造器、equaltoString方法的时间。除此之外,通过@Builder注解,lombok还可以方便的实现建造者模式。

    认识@Builder注解

    lombok注解在java进行编译时进行代码的构建,对于java对象的创建工作它可以更优雅,不需要写多余的重复的代码,这对于JAVA开发人员是很重要的,在出现lombok之后,对象的创建工作更提供Builder方法,它提供在设计数据实体时,对外保持private setter,而对属性的赋值采用Builder的方式,这种方式最优雅,也更符合封装的原则,不对外公开属性的写操作!
    @Builder声明实体,表示可以进行Builder方式初始化,@Value注解,表示只公开getter,对所有属性的setter都封闭,即private修饰,所以它不能和@Builder现起用

    简单使用

    在项目生成的实体类上,只需要我们添加@Builder注解即可。示例代码:

    package com.zy.pagehelper.model;
    
    import lombok.Builder;
    import lombok.Data;
    
    import java.io.Serializable;
    
    @Data
    @Builder
    public class Admin implements Serializable {
    
        private Long id;
    
        private String department;
    
        private String email;
    
        private String encodedpassword;
    
        private String name;
    
        private String username;
    
        private static final long serialVersionUID = 1L;
    }
    

    项目中使用。代码实例:

     Admin admins =  Admin.builder()
                          .id(admin.getId())
                          .name(admin.getName())
                          .email(admin.getEmail())
                          .department(admin.getDepartment())
                          .username(admin.getUsername())
                          .build();
    

    根据上面的示例,我们可以对@Builder注解有一个简单的认识。当我们向一个对象赋值的时候,可以通过@Builder注解类似于链式的调用对象进行赋值。它的主要优点就是可以优雅的给对象赋值,修改对象,省去了set方法来定义属性内容。

    深入探究--原理

    如果对建造者设计模式不太清楚的,可以先了解一下:建造者模式

    @Builder注释为你的类生成相对略微复杂的构建器API@Builder可以让你以下面显示的那样类似于链式的调用你的代码,来初始化你的实例对象:

     Admin admins =  Admin.builder()
                          .id(admin.getId())
                          .name(admin.getName())
                          .email(admin.getEmail())
                          .department(admin.getDepartment())
                          .username(admin.getUsername())
                          .build();
    

    @Builder可以放在类,构造器或方法上。虽然“基于类”和“基于构造器”模式是最常见的用例,但使用“方法”用例最容易解释。
    @Builder注解的方法(从现在开始称为target)将生成以下7件事:

    1. 一个内部静态类,名为FooBuilder,其类型参数与静态方法相同(称为builder)
    2. 在构建器中:目标的每个参数有一个私有的非静态非最终字段
    3. 在builder中:包私有的无参数空构造函数
    4. 在builder中:对目标的每个参数使用类似于“ setter”的方法:与该参数具有相同的类型和相同的名称。如上例所示,它返回构建器本身,以便可以将setter调用链接起来
    5. 在builder中:build()调用该方法的方法,并在每个字段中传递。它返回与目标返回相同的类型
    6. 有意义的toString()实现
    7. 在包含target的类中:一个builder()方法,该方法创建builder的新实例

    下面我们通过class类,与我们上面的每一条进行对比:

    @Builder
    public class Card {
        private int id;
        private String name;
        private boolean sex;
    }
    

    使用@Builder注解反编译后的class类:

    public class Card {
        private int id;
        private String name;
        private boolean sex;
    
        Card(int id, String name, boolean sex) {
            this.id = id;
            this.name = name;
            this.sex = sex;
        }
    	//注解在编译后使得Card类中多了一个名为Card.CardBuilder的静态内部类
        public static Card.CardBuilder builder() {
            return new Card.CardBuilder();
        }
    
        public static class CardBuilder {
            private int id;
            private String name;
            private boolean sex;
    
            CardBuilder() {
            }
    	//通过静态内部类,实现了name、sex、id等的属性方法
    	//其实这些方法和setAttribute十分类似,只是额外返回了实例本身,这使得它可以使用类似于链式调用的写法。
            public Card.CardBuilder id(int id) {
                this.id = id;
                return this;
            }
    
            public Card.CardBuilder name(String name) {
                this.name = name;
                return this;
            }
    
            public Card.CardBuilder sex(boolean sex) {
                this.sex = sex;
                return this;
            }
    	//build方法调用Card类的全参构造方法来生成Card实例
    	//Card类还是实现了builder方法,这个方法生成一个空的Card.CardBuilder实例。
            public Card build() {
                return new Card(this.id, this.name, this.sex);
            }
    
            public String toString() {
                return "Card.CardBuilder(id=" + this.id + ", name=" + this.name + ", sex=" + this.sex + ")";
            }
        }
    }
    

    使用@Builder注解有无继承

    一、 无继承父类的情况

    可以将@Builder注解直接放置在类上,示例代码:

    @Data
    @Builder
    public class Student {
    
        private String schoolName;
        private String grade;
    
        public static void main(String[] args) {
    
            Student student = Student.builder().schoolName("清华附小").grade("二年级").build();
            // Student(schoolName=清华附小, grade=二年级)
            System.out.println(student);
        }
    }
    

    二、有继承父类的情况

    1. 对于父类,使用@AllArgsConstructor注解
    2. 对于子类,手动编写全参数构造器,内部调用父类全参数构造器,在子类全参数构造器上使用@Builder注解
      通过这种方式,子类Builder对象可以使用父类的所有私有属性。但是这种解法也有两个副作用:
    • 因为使用@AllArgsConstructor注解,父类构造函数字段的顺序由声明字段的顺序决定,如果子类构造函数传参的时候顺序不一致,字段类型还一样的话,出了错不好发现
    • 如果父类字段有增减,所有子类的构造器都要修改

    示例代码父类

    @Data
    // 对于父类,使用@AllArgsConstructor注解
    @AllArgsConstructor
    public class Person {
    
        private int weight;
        private int height;
    }
    

    示例代码子类

    @Data
    @ToString(callSuper = true)
    public class Student extends Person {
    
        private String schoolName;
        private String grade;
    
    // 对于子类,手动编写全参数构造器,内部调用父类全参数构造器,在子类全参数构造器上使用@Builder注解
        @Builder
        public Student(int weight, int height, String schoolName, String grade) {
            super(weight, height);
            this.schoolName = schoolName;
            this.grade = grade;
        }
    
        public static void main(String[] args) {
    
            Student student = Student.builder().schoolName("清华附小").grade("二年级")
                    .weight(10).height(10).build();
    
            // Student(super=Person(weight=10, height=10), schoolName=清华附小, grade=二年级)
            System.out.println(student.toString());
        }
    }
    

    @Builder注解导致默认值无效问题

    @Builder注解导致默认值无效---解决方案

    看完上面的内容我们知道@Builder注解它可以让我们很方便的使用builder模式构建对象。但是当我们给对象赋有默认值的时候会被@Builder注解清除掉。
    从下面一段代码中,我们可以更加清楚的认识到这一点:

    public class BuilderTest {
        @lombok.Builder
        @lombok.Data
        private static class Builder {
            private String name = "1232";
        }
    
        @Test
        public void test() {
            Builder builder = Builder.builder().build();
            System.out.println(builder.getName());
        }
    }
    ---打印结果---
    null
    

    那么不想让这个默认值被清除,就只能用另外一个注解来对属性进行设置:@lombok.Builder.Default

    public class BuilderTest {
        @lombok.Builder
        @lombok.Data
        private static class Builder {
            @lombok.Builder.Default
            private String name = "1232";
        }
    
        @Test
        public void test() {
            Builder builder = Builder.builder().build();
            System.out.println(builder.getName());
        }
    }
    ---打印结果---
    1232
    
    • 需要注意的是@lombok.Builder.Default这个注解是后来才有的,目前已知的是1.2.X没有,1.6.X中有这个注解。

    @Builder注解导致默认值无效----分析原因

    由上面文章内容,我们可以知道,当我们使用@Builder注解时,编译后会生成一个静态内部类,通过静态内部类,最终才实现属性的方法,但是现实的方法,并没有默认值,这就导致当我们builder之后的实体类的属性值是null。

    示例代码:

    //编译前
    @lombok.Builder
    class Example {
        private String name = "123";
    }
    //编译后class类
    class Example {
        private String name;
    
        private Example(String name) {
            this.name = name;
        }
    
        public static ExampleBuilder builder() {
            return new ExampleBuilder();
        }
    
        public static class ExampleBuilder {
            private String name;
    
            private ExampleBuilder() {}
    		//这里我们可以看到,静态内部类实现了属性方法,但是并没有对默认值做处理,
    		//所有builder之后返回的属性值为null
            public ExampleBuilder name(String name) {
                this.name = name;
                return this;
            }
    
            @java.lang.Override public String toString() {
                return "Example(name = " + name + ")";
            }
    
            public Example build() {
                return new Example(name);
            }
        }
    }
    

    推荐参考blog@Builder注解构造器生成的详解

    @Builder相关注解

    @Builder.Default 使用

    比如有这样一个实体类:

    @Builder
    @ToString
    public class User {
        @Builder.Default
        private final String id = UUID.randomUUID().toString();
        private String username;
        private String password;
        @Builder.Default
        private long insertTime = System.currentTimeMillis();
    }
    

    在类中我在idinsertTime上都添加注解@Builder.Default,当我在使用这个实体对象时,我就不需要在为这两个字段进行初始化值,如下面这样:

    public class BuilderTest {
        public static void main(String[] args) {
            User user = User.builder()
                    .password("jdkong")
                    .username("jdkong")
                    .build();
            System.out.println(user);
        }
    }
    
    // 输出内容:
    User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8, username=jdkong, password=jdkong, insertTime=1546869309868)
    

    lombok在实例化对象时就为我们初始化了这两个字段值。
    当然,你如果再对这两个字段进行设值的话,那么默认定义的值将会被覆盖掉,如下面这样:

    public class BuilderTest {
        public static void main(String[] args) {
            User user = User.builder()
                    .id("jdkong")
                    .password("jdkong")
                    .username("jdkong")
                    .build();
            System.out.println(user);
        }
    }
    // 输出内容
    User(id=jdkong, username=jdkong, password=jdkong, insertTime=1546869642151)
    

    @Builder 详细配置

    @Target({TYPE, METHOD, CONSTRUCTOR})
    @Retention(SOURCE)
    public @interface Builder {
    	// 如果@Builder注解在类上,可以使用 @Builder.Default指定初始化表达式
    	@Target(FIELD)
    	@Retention(SOURCE)
    	public @interface Default {}
    	// 指定实体类中创建 Builder 的方法的名称,默认为: builder (个人觉得没必要修改)
    	String builderMethodName() default "builder";
    	// 指定 Builder 中用来构件实体类的方法的名称,默认为:build (个人觉得没必要修改)
    	String buildMethodName() default "build";
    	// 指定创建的建造者类的名称,默认为:实体类名+Builder
    	String builderClassName() default "";
    	// 使用toBuilder可以实现以一个实例为基础继续创建一个对象。(也就是重用原来对象的值)
    	boolean toBuilder() default false;
    	
    	@Target({FIELD, PARAMETER})
    	@Retention(SOURCE)
    	public @interface ObtainVia {
    		// 告诉lombok使用表达式获取值
    		String field() default "";
    		// 告诉lombok使用表达式获取值
    		String method() default "";
    
    		boolean isStatic() default false;
    	}
    }
    

    @Builder 全局配置

    # 是否禁止使用@Builder
    lombok.builder.flagUsage = [warning | error] (default: not set)
    #是否使用Guaua
    lombok.singular.useGuava = [true | false] (default: false)
    # 是否自动使用singular,默认是使用
    lombok.singular.auto = [true | false] (default: true)
    
  • 相关阅读:
    PyQt4信号与槽
    Amazon Redshift数据库
    NoSQL数据库的认识
    如何划分子网
    VPC见解
    Linux之添加交换分区
    MySQL基础之 标准模式通配符
    MySQL基础之 LIKE操作符
    MySQL基础之 AND和OR运算符
    MySQL基础之 索引
  • 原文地址:https://www.cnblogs.com/MrYuChen-Blog/p/13993196.html
Copyright © 2011-2022 走看看