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)

Lombok之@Builder注解的更多相关文章

  1. java~lombok里的Builder注解

    lombok注解在java进行编译时进行代码的构建,对于java对象的创建工作它可以更优雅,不需要写多余的重复的代码,这对于JAVA开发人员是很重要的,在出现lombok之后,对象的创建工作更提供Bu ...

  2. lombok @Builder注解使用的例子、反编译之后的代码详解

    lombok的@Builder实际是建造者模式的一个变种,所以在创建复杂对象时常使用 这里对lombok的@Builder和@Data组合的使用示例 import lombok.Builder; im ...

  3. lombok 下的@Builder注解用法

    pom依赖 <dependency> <groupId>org.projectlombok</groupId>            <artifactId& ...

  4. Lombok中的@Builder注解

    1.前言 今天在看项目代码的时候, 遇到了实体类上加@Builder注解, 之前在开发的时候, 一直没有用过这个注解, 便兴致勃勃地去查了一下资料, 它也是Lombok中的注解, 我们都知道Lombo ...

  5. Java中lombok @Builder注解使用详解(十八)

    Lombok大家都知道,在使用POJO过程中,它给我们带来了很多便利,省下大量写get.set方法.构造器.equal.toString方法的时间.除此之外,通过@Builder注解,lombok还可 ...

  6. 实体继承与@Builder注解共存

    在面向对象的设计里,继承是非常必要的,我们会把共有的属性和方法抽象到父类中,由它统一去实现,而在进行lombok时代之后,更多的打法是使用@Builder来进行对象赋值,我们直接在类上加@Builde ...

  7. lombok的@builder 不能新建DO对象 Lombok存在的一些问题

    1. 实体类加上 lombok的@builder之后  就不能新建对象了,,,构造函数被覆盖了? 加上两个标签之后解决 2.Lombok存在的一些问题 lombok问题 @Builder和@NoArg ...

  8. lombok的@Accessors注解

    @AllArgsConstructor @Data @NoArgsConstructor @Accessors(chain = true) @EqualsAndHashCode public clas ...

  9. 使用lombok的@Builder的注解的一个坑

    一开发说项目报错 java.lang.Long,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lan ...

随机推荐

  1. revel run报错 undefined: sys call.SIGUSR2"

    revel run报错,报错信息为 o Compilation Error (in ..\\..\\revel\\server_adapter_go.go:135): undefined: sysca ...

  2. Java线程池的四种创建方式

    Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程. newFix ...

  3. 如何計算n個圓的聯集面積

    如何計算n個圓的聯集面積 前言 一般人第一次遇到這個問題,可能會想要想辦法用排容原理,找圓之間交疊的凸包之類的.... 然而我只要舉一個例子,你就會發現我們就算把凸包找出來了,我們也非常難知道找到的凸 ...

  4. D. Road to Post Office 解析(思維)

    Codeforce 702 D. Road to Post Office 解析(思維) 今天我們來看看CF702D 題目連結 題目 略,請直接看原題. 前言 原本想說會不會也是要列式子解或者二分搜,沒 ...

  5. MongoDB Java连接---MongoDB基础用法(四)

    MongoDB 连接 标准 URI 连接语法: mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN ...

  6. 【总结】jvm

    一.jvm体系结构 1.jvm整体结构 jvm总体上是由类装载子系统(ClassLoader).运行时数据区.执行引擎三个部分组成. (jvm本质上就是一个java进程) 2.jvm生命周期 (1)j ...

  7. 云计算管理平台之OpenStack网络服务neutron

    一.简介 neutron的主要作用是在openstack中为启动虚拟机实例提供网络服务,对于neutron来讲,它可以提供两种类型的网络:第一种是provider network,这种网络就是我们常说 ...

  8. Java学习的第四十六天

    1.例8.1例类 import java.util.Scanner; public class Cjava { public static void main(String[]args) { Time ...

  9. 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪

    原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...

  10. Redis学习五(Redis 阻塞的原因及其排查方向).

    一.慢查询 因为 Redis 是单线程的,大量的慢查询可能会导致 redis-server 阻塞,可以通过 slowlog get n 获取慢日志,查看详情情况. 二.bigkey 大对象 bigke ...