Lombok

一、为什么要使用Lombok

当属性多时会出现大量的 getter/setter 方法,冗余,且一旦修改属性,就需要修改对应的方法。

Lombok 可以提高开发效率。

  • @Setter @Getter 生成 get、set 方法
  • @NoArgsConstructor @AllArgsConstructor 生成无参、全参构造器
  • @ToString 生成toString方法,与序列化不同(序列化属性不是按照定义的顺序排列的,可以自行探讨)
    Tips: 不推荐使用 @Data 一股脑的使用这个的话会生成很多不必要,合理使用就好

二、Lombok原理

  • 运行时解析

运行时能够解析的注解,必须将 @Retention 设置为 RUNTIME,这样就可以通过反射拿到该注解。java.lang.reflect 反射包种提供了一个接口 AnnotatedElement,该接口定义了获取注解的几个方法:Class,Constructor,Field,Method 等,都实现了该接口。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
  • 编译时解析

javac 执行过程如下:

在这里插入图片描述

Lombok 本质上是实现了 “JSR 269 API” 的程序。在使用 javac 的过程中,它产生作用具体流程如下:

1)javac 对源码进行分析,生成了一个抽象的语法树(AST)

2) 运行过程中调用实现 “JSR 269 API” 的Lombok 程序

3)对得到的 AST 进行处理,找到 @Data 注解所在类对应的语法树并修改,增加 getter/setter 方法定义的相应树节点

4)javac 使用修改后的抽象语法树(AST)生成字节码文件,即给 class 增加新的结点(代码块)

在 Lombok 源码中,对应注解的实现都在 HandleXXX 中,比如 @Getter 注解的实现时 HandleGetter.handle()。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
	String staticConstructor() default "";
}

三、用法

添加依赖并安装插件:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>

@Getter/@Setter

此注解在属性上,可以为相应的属性自动生成 Getter/Setter 方法,还可以指定访问范围

1、注解在属性上

public class User1 {
    @Getter @Setter
    private Long id;
    @Getter(AccessLevel.PROTECTED)
    private String phone;
    private String password;
}

编译结果:

public class User1 {
    private Long id;
    private String phone;
    private String password;

    public User1() {
    }

    public Long getId() {
        return this.id;
    }

    public void setId(final Long id) {
        this.id = id;
    }

    protected String getPhone() {
        return this.phone;
    }
}

2、注解在类上,表示为类中的所有字段生成Getter&Setter方法

@Getter
@Setter
public class User1 {

    private Long id;
    @Getter(AccessLevel.PROTECTED)
    private String phone;
    @Setter(AccessLevel.NONE)
    private String password;
}

编译后:

public class User1 {
    private Long id;
    private String phone;
    private String password;

    public User1() {
    }

    public Long getId() {
        return this.id;
    }

    public String getPassword() {
        return this.password;
    }

    public void setId(final Long id) {
        this.id = id;
    }

    public void setPhone(final String phone) {
        this.phone = phone;
    }

    protected String getPhone() {
        return this.phone;
    }
}

@Getter(lazy = true)

标注字段为懒加载字段,懒加载字段在创建对象时不会进行初始化,而是在第一次访问的时候才会初始化,后面再次访问也不会重复初始化

public class User1 {

    private final List<String> cityList = getCityFromCache();

    private List<String> getCityFromCache() {
        System.out.println("get city from cache ...");
        return new ArrayList<>();
    }

    public static void main(String[] args) {
        // 初始化对象时会执行getCityFromCache()方法
        User1 user = new User1();
    }
}
    @Getter(lazy = true)
    private final List<String> cityList = getCityFromCache();

    private List<String> getCityFromCache() {
        System.out.println("get city from cache ...");
        return new ArrayList<>();
    }

    public static void main(String[] args) {
        // 不会执行getCityFromCache()方法
        User1 user = new User1();
    }

@ToString

使用此注解,生成toString()方法,默认情况下它会按顺序(以逗号分隔)打印你的类名称以及每个字段。可以这样设置不包含哪些字段,可以指定一个也可以指定多个@ToString(exclude = “id”) / @ToString(exclude = {“id”,“name”})

@ToString(of = {"s"})
public class User1 {

    static String s = "";
    private Long id;
    private String phone;
    private String password;
    private String salt;
}

编译后:

public class User1 {
    static String s = "";
    private Long id;
    private String phone;
    private String password;
    private String salt;

    public User1() {
    }

    public String toString() {
        return "User1(s=" + s + ")";
    }
}

如果继承的有父类的话,可以设置callSuper 让其调用父类的toString()方法,例如:@ToString(callSuper = true)

public class superUser {
    private String phone;
}

@ToString(callSuper = true)
public class User1 extends superUser {

    static String s = "";
    private Long id;
    private String password;
    private String salt;
}

编译后:

public class User1 extends superUser {
    static String s = "";
    private Long id;
    private String password;
    private String salt;

    public User1() {
    }

    public String toString() {
        String var10000 = super.toString();
        return "User1(super=" + var10000 + ", id=" + this.id + ", password=" + this.password + ", salt=" + this.salt + ")";
    }
}

@EqualsAndHashCode

通过判断两个对象的成员变量值是否相等,来判断这两个对象是否相等,要重写hashCode()和equals()方法。

用在上,生成hashCode()和equals()方法,默认情况下,它将使用所有非静态,非transient字段。但可以通过在可选的 exclude 参数中来排除更多字段。或者,通过在of参数中命名它们来准确指定希望使用哪些字段。

@EqualsAndHashCode
public class User implements Serializable{

    private static final long serialVersionUID = 6569081236403751407L;

    private Long id;

    private String phone;

    private transient int status;
}

编译后:

public class User implements Serializable {
    private static final long serialVersionUID = 6569081236403751407L;
    private Long id;
    private String phone;
    private transient int status;

    public User() {
    }

    public boolean equals(Object o) {
        // 判断两个对象是不是同一个对象
        if(o == this) {
            return true;
            // 判断 o 是不是 User 的一个实例
        } else if(!(o instanceof User)) {
            return false;
        } else {
            User other = (User)o;
            // 判断两个对象是否可以比较
            if(!other.canEqual(this)) {
                return false;
            } else {
                Long this$id = this.id;
                Long other$id = other.id;
                if(this$id == null) {
                    if(other$id != null) {
                        return false;
                    }
                } else if(!this$id.equals(other$id)) {
                    return false;
                }

                String this$phone = this.phone;
                String other$phone = other.phone;
                if(this$phone == null) {
                    if(other$phone != null) {
                        return false;
                    }
                } else if(!this$phone.equals(other$phone)) {
                    return false;
                }

                return true;
            }
        }
    }

/**
 * 判断这个对象是不是 User 的实例
 */
    protected boolean canEqual(Object other) {
        return other instanceof User;
    }

    public int hashCode() {
        boolean PRIME = true;
        byte result = 1;
        Long $id = this.id;
        int result1 = result * 59 + ($id == null?43:$id.hashCode());
        String $phone = this.phone;
        result1 = result1 * 59 + ($phone == null?43:$phone.hashCode());
        return result1;
    }
}

@Data

@Data 注解在上, 相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,会为类的所有属性自动生成 getter/setter,equals,canEqual,hashCode,toString 方法,如为 final 属性,则不会生成 setter 方法

@Data
public class User {
    /**
     * @Data 只对成员变量起作用,
     */
    static String s = "";
    /**
     * 由于是final修饰的成员变量,不可更改,只会生成get,不会有set
     */
    final int id2;
    @NonNull
    private Integer id;
    private String userName;
}

@NonNull

用在属性构造器上,为字段赋值时(即调用字段的setter方法时),如果传的参数为null,则会抛出空异常NullPointerException,生成setter方法时会对参数是否为空检查

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
  }
}

编译后:

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person");
    }
    this.name = person.getName();
  }
}

@NoArgsConstructor

生成一个无参构造方法。当类中有final字段没有被初始化时,编译器会报错,此时可用@NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0 / false / null, 这样编译器就不会报错。对于具有约束的字段(例如@NonNull字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。

@NoArgsConstructor(force = true)
public class User {
    private Long id;
    @NonNull
    private String phone;
    private final Integer age;
}

编译后:

public class User {
    private Long id;
    @NonNull
    private String phone;
    private final Integer age = null;

    public User() {
    }
}

@RequiredArgsConstructor

对指定的参数生成构造方法。

生成构造方法(可能带参数也可能不带参数),如果带参数,这参数只能是以final修饰的未经初始化的字段(若用final修饰了,就不会再改变,生成构造方法就没意义),或者是以@NonNull注解的未经初始化的字段。

@RequiredArgsConstructor
public class User1  {

    private Long id;

    @NonNull
    private String phone;

    @NotNull
    private Integer status = 0;

    private final Integer age;
    private final String country = "china";
}

编译后:

public class User1 {
    private Long id;
    @NonNull
    private String phone;
    @NotNull
    private Integer status = 0;
    private final Integer age;
    private final String country = "china";

    public User1(@NonNull final String phone, final Integer age) {
        if (phone == null) {
            throw new NullPointerException("phone is marked non-null but is null");
        } else {
            this.phone = phone;
            this.age = age;
        }
    }
}

@RequiredArgsConstructor(staticName = “of”) 会生成一个 of() 的静态方法,并把构造方法设置为私有的

@RequiredArgsConstructor(staticName = "of")
public class User1 {
    private Long id;

    @NonNull
    private String phone;

    @NotNull
    private Integer status = 0;

    private final Integer age;
    private final String country = "china";
}

编译后:

public class User1 {
    private Long id;
    @NonNull
    private String phone;
    @NotNull
    private Integer status = 0;
    private final Integer age;
    private final String country = "china";

    private User1(@NonNull final String phone, final Integer age) {
        if (phone == null) {
            throw new NullPointerException("phone is marked non-null but is null");
        } else {
            this.phone = phone;
            this.age = age;
        }
    }

    public static User1 of(@NonNull final String phone, final Integer age) {
        return new User1(phone, age);
    }
}

@AllArgsConstructor

用在上,生成一个全参数的构造方法

@AllArgsConstructor
public class User {
    private Long id;

    @NonNull
    private String phone;

    @NotNull
    private Integer status = 0;

    private final Integer age;
    private final String country = "china";
}

编译后:

public class User1 {
    private Long id;
    @NonNull
    private String phone;
    @NotNull
    private Integer status = 0;
    private final Integer age;
    private final String country = "china";

    public User1(final Long id, @NonNull final String phone, final Integer status, final Integer age) {
        if (phone == null) {
            throw new NullPointerException("phone is marked non-null but is null");
        } else {
            this.id = id;
            this.phone = phone;
            this.status = status;
            this.age = age;
        }
    }
}

@Value

用在上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法

@Value
publicclassLombokDemo{
	@NonNull
    private int id;
}

编译后:

publicclassLombokDemo {    
    private final int id;
    public int getId() {
        return this.id;
    }
}

@Builder

Builder 使用创建者模式又叫生成器模式(Builder Pattern)。简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程。

原理:

public class User1 {
    private Integer id;
    private String name;

    User1(final Integer id, final String name) {
        this.id = id;
        this.name = name;
    }
// 在实体类中:会创建一个builder()方法,它的目的是用来创建构建器。
    public static User1.User1Builder builder() {
        return new User1.User1Builder();
    }
    
// 内部静态类,具有和实体类相同的属性(成为构建器)
    public static class User1Builder {
        // 在构建器中:对于目标类中的所有的属性和未初始化的final字段,都会在构建器中创建对应属性。
        private Integer id;
        private String name;
		// 创建一个无参的default构造函数。
        User1Builder() {
        }

        // 对于实体类中的每个参数,都会对应创建类似于setter的方法,只不过方法名与该参数名相同。 
        // 并且返回值是构建器本身(便于链式调用)
        public User1.User1Builder id(final Integer id) {
            this.id = id;
            return this;
        }

        public User1.User1Builder name(final String name) {
            this.name = name;
            return this;
        }

        // 调用此方法,就会根据设置的值进行创建实体对象
        public User1 build() {
            return new User1(this.id, this.name);
        }

        public String toString() {
            return "User1.User1Builder(id=" + this.id + ", name=" + this.name + ")";
        }
    }
}

常规用法:

@Data
public class User1 {
    private Integer id;
    private String name;
    private String address;

    public static void main(String[] args) {
        User1 user1 = new User1(1, "java", "china");
        user1.setId(1);
        user1.setName("11");
        user1.setAddress("111");
        System.out.println(user1);
    }
}

使用 @Builder:

@Builder
@Data
public class User1 {
    private Integer id;
    private String name;
    private String address;

    public static void main(String[] args) {
        User1 user1 = User1.builder().id(1).address("11").name("111").build();
        System.out.println(user1);
    }
}

使用@Builder时如何解决继承关系?

1)在父类上,使用**@AllArgsConstructor**注解。

2)在子类上,手动编写全参数构造器,内部调用父类全参数构造器,在子类全参数构造器上使用@Builder注解。

@ToString
@AllArgsConstructor
public class SuperUser {
    private String phone;
}

@ToString(callSuper = true)
public class User1 extends SuperUser{

    private Integer id;

    private String name;

    private String address;


    @Builder
    public User1(String phone, Integer id, String name, String address) {
        super(phone);
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public static void main(String[] args) {
        User1 user1 = User1.builder().phone("12346").id(1).address("11").name("111").build();
        System.out.println(user1);
    }
}

输出:

User1(super=SuperUser(phone=12346), id=1, name=111, address=11)

副作用:

  • 因为使用@AllArgsConstructor注解,父类构造函数字段的顺序由声明字段的顺序决定,如果子类构造函数传参的时候顺序不一致,字段类型还一样的话,出了错不好发现
  • 如果父类字段有增减,所有子类的构造器都要修改

@Builder.Default

默认赋值

@Builder
@ToString
public class User1 {

    private Integer id;
    private String name;

    @Builder.Default
    private String address = "南京";

    public static void main(String[] args) {
        User1 user1 = User1.builder().id(1).name("jack").build();
        System.out.println(user1);
    }
}

// 输出:User1(id=1, name=jack, address=南京)

当然,如果再对这两个字段进行设值的话,那么默认定义的值将会被覆盖掉

@Log

生成log对象,用于记录日志,可以通过topic属性来设置getLogger(String name)方法的参数 例如 @Log4j(topic = “com.xxx.entity.User”),默认是类的全限定名,即 类名.class,log支持以下几种:

  • @Log java.util.logging.Logger
  • @Log4j org.apache.log4j.Logger
  • @Log4j2 org.apache.logging.log4j.Logger
  • @Slf4j org.slf4j.Logger
  • @XSlf4j org.slf4j.ext.XLogger
  • @CommonsLog org.apache.commons.logging.Log
  • @JBossLog org.jboss.logging.Logger
@Data
@Builder
@Log
public class User1 {

    private Integer id;
    private String name;
    private String address;

    public void test(@NonNull String s) {
        log.info("test");
        System.out.println(s);
    }

    public static void main(String[] args) {
        User1 user1 = User1.builder().id(1).address("11").name("111").build();
        user1.test("ssfsdfj");
    }
}

补充:日志打印规范

// 要么不填充变量(只有异常,就不能使用占位符)
log.error("xxxx",e)
// 要么填充业务字段
log.error("xxxx{}",var,e)
    
// 建议包含当前处理的参数id(定位问题时确认是哪条数据),同时带上异常堆栈,用slf4j,例如:
log.error("编号[{}]的数据处理失败!",ywbh,e);    

@Clenaup

用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流

    public static void main(String[] args) throws IOException {
        @Cleanup InputStream in = new FileInputStream("E:\\filepath.txt");
        @Cleanup OutputStream out = new FileOutputStream("E:\\path2.txt");
        byte[] b = new byte[1000];
        while (true) {
            int r = in.read(b);
            if (r == -1) {
                break;
            }
            out.write(b, 0, r);
        }
    }

编译后:

public static void main(String[] args) throws IOException {
    FileInputStream in = new FileInputStream("E:\\filepath.txt");
    try {
        FileOutputStream out = new FileOutputStream("E:\\path2.txt");
        try {
            byte[] b = new byte[1000];
            while(true) {
                int r = in.read(b);
                if (r == -1) {
                    return;
                }
                out.write(b, 0, r);
            }
        } finally {
            if (Collections.singletonList(out).get(0) != null) {
                out.close();
            }
        }
    } finally {
        if (Collections.singletonList(in).get(0) != null) {
            in.close();
        }
    }
}

或者使用 try-with-resources

@UtilityClass

添加static,同时构建了一个私有构造方法。

@UtilityClass
public class FileHandler {
    public void test(){}
} 

编译后:

public final class FileHandler {
    public static void test() {
    }

    private FileHandler() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
}

others(不推荐)

@Delegate

为List类型的字段生成一大堆常用的方法,其实这些方法都是List中的方法

public class User {
    @Delegate
    private List<String> address;
}

@Wither

提供了给final字段赋值的一种方法(@Deprecated)

@SneakyThrows

使用try catch 来捕获异常, 默认捕获的是Throwable异常,也可以设置要捕获的异常 (不易读)

    @SneakyThrows
    public void sleep(){
        Thread.sleep(1000);
    }

    @SneakyThrows(InterruptedException.class)
    public void sleep2()  {
        Thread.sleep(1000);
    }

编译后:

    public void sleep() {
        try {
            Thread.sleep(1000L);
        } catch (Throwable var2) {
            throw var2;
        }
    }

    public void sleep2() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException var2) {
            throw var2;
        }
    }

val/var

作为局部变量声明的类型,而不必编写实际类型。val/var 将从初始化程序表达式中推断类型。

var和val的差别在于,val修饰的局部变量没有被标记为final。

final String address = "123";
address = "34";	// 标红

val address1 = "123";
address1 = "34";	// 编译时才会报错

补充:从 Java10 开始支持使用 var 定义局部变量:var相当于一个动态类型,使用 var 定义的局部变量的类型由编译器自动推断。

注:Java 的 var 与 JavaScript 的 var 截然不同,JavaScript 本质上是弱类型语言,因此JavaScript使用 var 定义的变量并没有明确的类型,但 Java 是强类型语言,即便使用 var 定义变量,但依然有明确的类型——为局部变量指定初始值时,该变量的类型就确定了。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值