lombok 的使用

31 篇文章 4 订阅
2 篇文章 0 订阅

lombok 包里注解的使用

https://blog.csdn.net/weixin_42272869/article/details/122320906

在做项目的时候,我们一般要在entity里面写大量的get/set/构造方法,不仅代码众多,而且看起来很low

针对这个情况,lombok应运而生,我们经常使用这个包,来总结一下这个包里面的一些常用的注解

1、导包

在项目的pom.xml 文件夹中导入包。

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

下面的注解源码根据 maven仓库 lombok最多使用的版本1.18.12为例子。可以看到,引用的时候,scope=provided,说明它只在编译阶段生效,不需要打入包中。

其实Lombok的原理就是在编译期将使用了Lombok注解的Java文件正确编译为完整的Class文件,可以通过反编译来将class文件转换为Java文件,看其具体是怎么处理的

2、使用

2.0 Lombok实现原理

自Java 6开始,javac编译就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

Lombok实际上就是一个实现了"JSR 269 API" 的程序。在使用javac的过程中,它产生作用的具体流程如下:

  • 首先,javac对源代码进行分析,对源代码进行编译生成一棵抽象语法树(AST)
  • 然后,javac在编译过程中调用实现了JSR 269的Lombok程序
  • 然后,Lombok对第一步骤得到的AST进行处理,找到使用了Lombok注解的类对应的语法树 (AST),然后对该语法树(AST)进行修改,增加Lombok各个注解定义的相应树节点
  • 最后,javac使用修改后的抽象语法树(AST)生成字节码文件

2.1 IDEA支持Lombok

在idea 设置中安装Lombok插件。 File----->Settings------>Plugins

防止一错误

请添加图片描述

开启虚Anonotation Processors ,让Lombok注解在编译阶段起到作用,也方便获取到class文件能反编译到Java文件看编译后的代码来查看lombok如何工作的。

请添加图片描述

2.2 注解

这个包的注解其实也有很多,从lombok 1.18.12包里可以看到一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIs6EEqw-1641371709018)(C:\Users\Administrator\Desktop\sx\photo\lombok_3.png)]

这边主要介绍一下常用的一些注解,下面再分别介绍

注解名称可使用的地方描述
Data在 类、接口、枚举 使用它包含Getter、Setter、RequiredArgsConstructor、ToString、@EqualsAndHashCode等注解,即当使用当前注解时会自动生成包含的这五个注解对应所有方法;
Getter在 类、接口、枚举、属性 使用属性的get方法,如果标注在类上时为所有成员变量生成 getter 和 setter 方法。注解在成员变量上时只为该字段生成 getter 和 setter 方法
Setter在 类、接口、枚举、属性 使用属性的set方法,如果标注在类上时为所有成员变量生成 getter 和 setter 方法。注解在成员变量上时只为该字段生成 getter 和 setter 方法
NoArgsConstructor在 类、接口、枚举 使用类的无参构造方法
AllArgsConstructor在 类、接口、枚举 使用类的所有属性的构造方法
RequiredArgsConstructor在 类、接口、枚举 使用生成构造方法,仅包含final和@NonNull注解的成员变量
EqualsAndHashCode在 类、接口、枚举 使用重写toString和hashcode方法
ToString在 类、接口、枚举 使用toString方法
Cleanup在 局部变量 中使用自动关闭资源,针对实现了java.io.Closeable接口的对象有效
SneakyThrows在 方法 构造方法 中使用可以对受检异常进行捕捉并抛出
Builder在类 方法 构造方法 中使用使用构造者模式
2.2.1 注解的介绍
// 表明注解可以使用在哪里
// 这里表示注解可以使用在 方法 属性  注解  构造方法  参数  type 
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
//描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间
@Retention(RetentionPolicy.RUNTIME)
//表明这个注解是由 javadoc记录的
@Documented
public @interface MyOwnAnnotation {
    
}
2.2.2 注解详细属性的介绍
@Getter、@Setter
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
    // 指定access级别,默认生成方法是PUBLIC级别
    AccessLevel value() default AccessLevel.PUBLIC;
    
    Getter.AnyAnnotation[] onMethod() default {};
    
    // 是否是懒加载
    boolean lazy() default false;
    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {
    }
}

AccessLevel:通过这个参数可以指定生成的方法的访问级别。一共有六种级别:

AccessLevel描述
PUBLIC生成 public 修饰的 getter 或 setter 方法
MODULE生成没有修饰符修饰的 getter 或 setter 方法
PROTECTED生成 protected 修饰的 getter 或 setter 方法
PACKAGE生成没有修饰符修饰的 getter 或 setter 方法
PRIVATE生成 private 修饰的 getter 或 setter 方法
NONE不生成 getter 或 setter 方法
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

用于生成不同的构造函数。有staticName、access 等属性。可通过staticName属性来创建静态工厂方法。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
    // (1)如果设置该属性,则生成的构造函数将会变为私有的,另外还会生成一个静态“构造函数”。该静态函数内部会调用私有的构造函数;如果该属性没有被指定值,则不起作用
    String staticName() default "";

    NoArgsConstructor.AnyAnnotation[] onConstructor() default {};
    
	// 设置生成的构造方法的访问级别,默认是public
    AccessLevel access() default AccessLevel.PUBLIC;
    
	//这个字段在 RequiredArgsConstructor AllArgsConstructor 是没有的
    //如果设置为 true,会将所有 final 字段初始化为 0 / null / false。否则,编译时出现错误。
    boolean force() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {
    }
}
@EqualsAndHashCode与@ToString

该两个注解都有exclude与of来指定重写/Tostring的排除规则/包含规则

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
    // 该属性设置为 false 表示输出没有属性名和等号,只有属性值,多个属性值用逗号隔开。
    boolean includeFieldNames() default true;
	// 除去哪个属性,传入属性名称
    String[] exclude() default {};
	// 包含哪个属性,传入属性名称
    String[] of() default {};
	// 是否包含父类的toString
    boolean callSuper() default false;
	// 是否使用get方法获取属性值 通常都是通过字段的 getter 方法获取字段值,如果没有 getter 方法,才在通过直接访问字段来获取值。该属性设置为 true,表示输出的字段值不通过 getter 方法获取,而是直接访问字段,默认为 false。
    boolean doNotUseGetters() default false;
	// 该属性设置为 true,不输出任何字段信息,只输出了构造方法的名字,默认为 false。
    boolean onlyExplicitlyIncluded() default false;

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {
    }

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        int rank() default 0;

        String name() default "";
    }
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
    // 除去哪个属性,传入属性名称
    String[] exclude() default {};
	// 包含哪个属性,传入属性名称
    String[] of() default {};
	// 是否包含父类
    boolean callSuper() default false;
	// 是否使用get方法获取属性值
    boolean doNotUseGetters() default false;
	// 废弃
    EqualsAndHashCode.AnyAnnotation[] onParam() default {};
    
	// 该属性设置为 true,不输出任何字段信息,只输出了构造方法的名字,默认为 false。
    boolean onlyExplicitlyIncluded() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {
    }

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {
    }

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        String replaces() default "";
    }
}

// 使用实例
@ToString(of = "id" , exclude = "name") // 除去name属性的toString方法,只打印id属性
class Study{
    
    Integer id;
    
    String name;
    
    String sex;
}
@SneakyThrows

可指定异常,出现该异常则进行抛出,不指定默认是所有

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
    Class<? extends Throwable>[] value() default {Throwable.class};
}

//使用示例
@SneakyThrows(value = {IOException.class}) // 抛出IOException
public String example() {
    File file = new File("test.txt");
    @Cleanup InputStream inputStream = new FileInputStream(file);
    int len;
    byte[] bytes = new byte[1024];
    while ((len = inputStream.read(bytes)) != -1) {
        System.out.println("content : " + new String(bytes, 0, len));
    }
    return "{\"result\":\"success\"}";
}

等同于----->
public String example() throws IOException{
    File file = new File("test.txt");
    @Cleanup InputStream inputStream = new FileInputStream(file);
    int len;
    byte[] bytes = new byte[1024];
    while ((len = inputStream.read(bytes)) != -1) {
        System.out.println("content : " + new String(bytes, 0, len));
    }
    return "{\"result\":\"success\"}";
}
@Cleanup

用在局部变量上,用于资源的关闭,提供value字段来指定 方法

@Target({ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {
    // 这个是方法名称,如果关闭的对象,里面关闭流的方法是close1,那这里就可以指定为close1
    // 一般都是close方法作为
    String value() default "close";
}

// 使用示例
@Cleanup(value = "delete") File file = new File("test.txt"); // 使用file后,就删除file,删除file是delete方法,所以这里value是delete
@Cleanup InputStream inputStream = new FileInputStream(file); // 使用流后,就关闭流操作
@Builder

可以使用在类上、构造方法、普通方法上。

  • 如果在类上使用这个注解,则会类似@AllArgsConstructor(access = AccessLevel.PRIVATE),生成一个私有的构造方法,包括该类所有参数。
    • 注意,只有当该类中没有编写任何参数的构造函数,也没有使用者几个@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructo注解的情况下才会生成这个包含所有参数的私有构造函数。
  • 使用该注解,除了会生成一个私有的构造函数,还会生成一个名为 XXBuilder 的内部类和一个静态工厂方法 builder()。
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
    // 该属性用于指定生成的静态工厂方法的名称。(1)不使用该属性的情况下,生成的静态工厂方法默认名称为 builder。(2)使用该属性,如果值为空串,则不会生成静态工厂方法
    String builderMethodName() default "builder";
	// 该属性用于指定静态内部类中用于获取外部使用@Builder注解的类实例的方法名称。(1)不使用该属性的情况下,生成的方法默认名称为 build。(2)使用该属性,如果值为空串,编译会报错
    String buildMethodName() default "build";
    
	// 该属性用于指定生成的静态内部类的名称。如果值为空串,则相当于未使用该属性,则类名称采用  使用注解的类名称+builder后缀
     String builderClassName() default "";
    
	// 默认为 false。如果为 true,则生成一个实例方法,以获取使用该实例的值初始化的生成器。仅当 @Builder 用于构造函数、类型本身或用于返回的静态方法时才是合法的声明类型的实例。
    
    boolean toBuilder() default false;
	// 设置生成的生成器类(静态内部类)的访问级别。默认情况下,生成的构建器类是 public 的。注意:如果是自己写的 builder 类,则不会改变它的访问级别。
    AccessLevel access() default AccessLevel.PUBLIC;

    String setterPrefix() default "";

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Default {
    }

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ObtainVia {
        String field() default "";

        String method() default "";

        boolean isStatic() default false;
    }
}

3、使用

(1)Getter、Settter

定义了三个属性,分别使用或者不使用@Getter @Setter,来查看预期是否相同

  • id属性:预期会生成对应的 默认 public 的 get、set方法
  • name属性:预期会生成对应的private 的 get、set方法
  • sex属性:预期不会生成get、set方法
public class ExampleGetterAndSetter {

    // 使用get、set注解
    // 预期:会生成对应的 默认 public  的 get、set方法
    @Getter
    @Setter
    private Integer id;

    // 使用get、set注解
    // 预期:会生成对应的private 的 get、set方法
    @Getter(value = AccessLevel.PRIVATE)
    @Setter(value = AccessLevel.PRIVATE)
    private String name;

    // 不使用get、set注解
    // 预期:不会生成对应的get、set方法
    private String sex;
}

编译文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

  • id属性:生成了对应的 默认 public 的 get、set方法,符合预期
  • name属性:生成了对应的private 的 get、set方法,符合预期
  • sex属性:没有生成get、set方法,符合预期
public class ExampleGetterAndSetter
{

    public ExampleGetterAndSetter()
    {
    }
    public Integer getId()
    {
        return id;
    }
    public void setId(Integer id)
    {
        this.id = id;
    }
    private String getName()
    {
        return name;
    }
    private void setName(String name)
    {
        this.name = name;
    }
    private Integer id;
    private String name;
    private String sex;
}

(2)@AllArgsConstructor @NoArgsConstructor RequiredArgsConstructor

  • NoArgsConstructor:预期会生成无参的构造函数,构造函数是公有的
  • AllArgsConstructor:预期会生成全参的构造函数,构造函数是私有的
  • RequiredArgsConstructor:预期会生成符合条件的构造函数,构造函数是私有的,有一个静态方法调用这个私有的构造函数
// 使用了 NoArgsConstructor
// 预期:生成无参的构造函数,构造函数是公有的
@NoArgsConstructor
// 使用了 AllArgsConstructor
// 预期:生成全参的构造函数,构造函数是私有的
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ExampleConstructor {

    private final String PUBLIC_NAME = "hahaha";

    private Integer id;

    private String name;

    private String sex;
}

// 使用了 RequiredArgsConstructor
// 预期:生成符合条件的构造函数,构造函数是私有的,有一个静态方法调用这个私有的构造函数
@RequiredArgsConstructor(staticName = "staticName")
class ExampleRequiredConstructor {
	// 因为 有RequiredArgsConstructor标注,所以这里虽然没有初始化,但是也会编译通过
    private final String PUBLIC_NAME;

    private Integer id;

    private String name;

    private String sex;
}

编译文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

  • 生成了共有的无参构造函数,符合预期
  • 生成了私有的全参构造函数,符合预期
  • 仅仅对final变量生成了私有的构造函数,并且有一个静态的方法来调用这个构造函数,符合预期
public class ExampleConstructor
{
	// 无参构造函数
    public ExampleConstructor()
    {
    }

    private ExampleConstructor(Integer id, String name, String sex)
    {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }

    private final String PUBLIC_NAME = "hahaha";
    private Integer id;
    private String name;
    private String sex;
}

class ExampleRequiredConstructor
{

    private ExampleRequiredConstructor(String PUBLIC_NAME)
    {
        this.PUBLIC_NAME = PUBLIC_NAME;
    }

    public static ExampleRequiredConstructor staticName(String PUBLIC_NAME)
    {
        return new ExampleRequiredConstructor(PUBLIC_NAME);
    }

    private final String PUBLIC_NAME;
    private Integer id;
    private String name;
    private String sex;
}

(3)Data

  • Getter:预期会生成属性的get方法
  • Setter:预期会生成属性的set方法
  • RequiredArgsConstructor:预期对符合要求的final与@NonNull标记的成员变量生成构造函数
  • ToString:预期会有toString方法
  • @EqualsAndHashCode:预期会有hashcode与equals方法
// 使用了 Data
// 预期:Getter、Setter、RequiredArgsConstructor、ToString、@EqualsAndHashCode的同理作用
@Data
public class ExampleData {
	// 这里初始化编译也是会正常运行的,因为@Data里面有RequiredArgsConstructor,对其生成构造函数
    private final String PUBLIC_NAME;

    private Integer id;

    private String name;

    private String sex;
}

编译文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

  • 属性有get与set方法,符合预期
  • 有final变量的构造函数,符合预期
  • 有equals与hashcode方法,符合预期
  • 有toString方法,符合预期
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ExampleData.java

package com.study.pojo;


public class ExampleData
{
	// 构造函数
    public ExampleData(String PUBLIC_NAME)
    {
        this.PUBLIC_NAME = PUBLIC_NAME;
    }

    public String getPUBLIC_NAME()
    {
        return PUBLIC_NAME;
    }

    public Integer getId()
    {
        return id;
    }

    public String getName()
    {
        return name;
    }

    public String getSex()
    {
        return sex;
    }

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

    public void setName(String name)
    {
        this.name = name;
    }

    public void setSex(String sex)
    {
        this.sex = sex;
    }
	// equals与hashcode方法
    public boolean equals(Object o)
    {
        if(o == this)
            return true;
        if(!(o instanceof ExampleData))
            return false;
        ExampleData other = (ExampleData)o;
        if(!other.canEqual(this))
            return false;
        Object this$PUBLIC_NAME = getPUBLIC_NAME();
        Object other$PUBLIC_NAME = other.getPUBLIC_NAME();
        if(this$PUBLIC_NAME != null ? !this$PUBLIC_NAME.equals(other$PUBLIC_NAME) : other$PUBLIC_NAME != null)
            return false;
        Object this$id = getId();
        Object other$id = other.getId();
        if(this$id != null ? !this$id.equals(other$id) : other$id != null)
            return false;
        Object this$name = getName();
        Object other$name = other.getName();
        if(this$name != null ? !this$name.equals(other$name) : other$name != null)
            return false;
        Object this$sex = getSex();
        Object other$sex = other.getSex();
        return this$sex != null ? this$sex.equals(other$sex) : other$sex == null;
    }

    protected boolean canEqual(Object other)
    {
        return other instanceof ExampleData;
    }

    public int hashCode()
    {
        int PRIME = 59;
        int result = 1;
        Object $PUBLIC_NAME = getPUBLIC_NAME();
        result = result * 59 + ($PUBLIC_NAME != null ? $PUBLIC_NAME.hashCode() : 43);
        Object $id = getId();
        result = result * 59 + ($id != null ? $id.hashCode() : 43);
        Object $name = getName();
        result = result * 59 + ($name != null ? $name.hashCode() : 43);
        Object $sex = getSex();
        result = result * 59 + ($sex != null ? $sex.hashCode() : 43);
        return result;
    }
	// toString方法
    public String toString()
    {
        return (new StringBuilder()).append("ExampleData(PUBLIC_NAME=").append(getPUBLIC_NAME()).append(", id=").append(getId()).append(", name=").append(getName()).append(", sex=").append(getSex()).append(")").toString();
    }

    private final String PUBLIC_NAME;
    private Integer id;
    private String name;
    private String sex;
}

(4)ToString EqualsAndHashCode

  • ToString:使用了ToString,预期会生成所有成员变量的toString方法
  • EqualsAndHashCode:预期会生成所有成员变量的hashcode与equals方法
// 使用了ToString
// 预期:会生成toString方法
@ToString
// EqualsAndHashCode
// 预期:会生成hashcode与equals方法
@EqualsAndHashCode
public class ExampleToStringAndHashCode {

    private Integer id;

    private String name;

    private String sex;

}

编译文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

  • 所有成员变量的toString方法。符合预期
  • 所有成员变量的hashcode与equals方法,符合预期
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ExampleToStringAndHashCode.java

package com.study.pojo;

public class ExampleToStringAndHashCode
{

    public ExampleToStringAndHashCode()
    {
    }

    public String toString()
    {
        return (new StringBuilder()).append("ExampleToStringAndHashCode(id=").append(id).append(", name=").append(name).append(", sex=").append(sex).append(")").toString();
    }

    public boolean equals(Object o)
    {
        if(o == this)
            return true;
        if(!(o instanceof ExampleToStringAndHashCode))
            return false;
        ExampleToStringAndHashCode other = (ExampleToStringAndHashCode)o;
        if(!other.canEqual(this))
            return false;
        Object this$id = id;
        Object other$id = other.id;
        if(this$id != null ? !this$id.equals(other$id) : other$id != null)
            return false;
        Object this$name = name;
        Object other$name = other.name;
        if(this$name != null ? !this$name.equals(other$name) : other$name != null)
            return false;
        Object this$sex = sex;
        Object other$sex = other.sex;
        return this$sex != null ? this$sex.equals(other$sex) : other$sex == null;
    }

    protected boolean canEqual(Object other)
    {
        return other instanceof ExampleToStringAndHashCode;
    }

    public int hashCode()
    {
        int PRIME = 59;
        int result = 1;
        Object $id = id;
        result = result * 59 + ($id != null ? $id.hashCode() : 43);
        Object $name = name;
        result = result * 59 + ($name != null ? $name.hashCode() : 43);
        Object $sex = sex;
        result = result * 59 + ($sex != null ? $sex.hashCode() : 43);
        return result;
    }

    private Integer id;
    private String name;
    private String sex;
}

当使用了里面的of与exclude属性时候:

  • ToString:预期会生成只包含id、name属性的toString方法,而且toString的输出只有值,值以逗号隔开

  • EqualsAndHashCode:预期会生成不包括id的hashcode与equals方法

// 使用了ToString
// 预期:只包含id、name属性的toString方法,而且toString的输出只有值
@ToString(includeFieldNames = false, of = {"id", "name"})
// EqualsAndHashCode
// 预期:会生成hashcode与equals方法,去除id的比对
@EqualsAndHashCode(exclude = "id")
class ExampleToStringAndHashCodeWith {

    private Integer id;

    private String name;

    private String sex;

}

反编译后的源代码,可以看到:

  • toString方法只有id与name属性,并且只有值,以逗号隔开。符合预期
  • hashcode与equals方法没有对id进行处理,符合预期
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ExampleToStringAndHashCode.java

package com.study.pojo;


class ExampleToStringAndHashCodeWith
{

    ExampleToStringAndHashCodeWith()
    {
    }

    public String toString()
    {
        return (new StringBuilder()).append("ExampleToStringAndHashCodeWith(").append(id).append(", ").append(name).append(")").toString();
    }

    public boolean equals(Object o)
    {
        if(o == this)
            return true;
        if(!(o instanceof ExampleToStringAndHashCodeWith))
            return false;
        ExampleToStringAndHashCodeWith other = (ExampleToStringAndHashCodeWith)o;
        if(!other.canEqual(this))
            return false;
        Object this$name = name;
        Object other$name = other.name;
        if(this$name != null ? !this$name.equals(other$name) : other$name != null)
            return false;
        Object this$sex = sex;
        Object other$sex = other.sex;
        return this$sex != null ? this$sex.equals(other$sex) : other$sex == null;
    }

    protected boolean canEqual(Object other)
    {
        return other instanceof ExampleToStringAndHashCodeWith;
    }

    public int hashCode()
    {
        int PRIME = 59;
        int result = 1;
        Object $name = name;
        result = result * 59 + ($name != null ? $name.hashCode() : 43);
        Object $sex = sex;
        result = result * 59 + ($sex != null ? $sex.hashCode() : 43);
        return result;
    }

    private Integer id;
    private String name;
    private String sex;
}

(5)@CleanUp@SneakyThrows

接口UserController 中方法来使用**@CleanUp**、@SneakyThrows注解。

我们源代码采用下面的第二种作为class转换代码的对比代码,其他几种是方便讲解贴上的

@RestController
public class UserController {
	(第一种,这个方法里面有受检异常FileNotFoundExceptionIOException,如果不用@SneakyThrows,需要try catch或者抛出)
    @GetMapping("/users/test")
    public String example() {
        File file = new File("test.txt");
        try {
            // 使用了Cleanup
            // 预期会有关闭流操作
            @Cleanup InputStream inputStream = new FileInputStream(file);
            int len;
            byte[] bytes = new byte[1024];
            while ((len = inputStream.read(bytes)) != -1) {
                System.out.println("content : " + new String(bytes, 0, len));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "{\"result\":\"success\"}";
    }
    (第二种,使用SneakyThrows,抛出IOException,因为IOException包含FileNotFoundException,所以你运行,是不会出现问题的)
    @GetMapping("/users/test")
    @SneakyThrows(value = {IOException.class})
    public String example() {
        File file = new File("test.txt");
        // 使用了Cleanup
            // 预期会有关闭流操作
        @Cleanup InputStream inputStream = new FileInputStream(file);
        int len;
        byte[] bytes = new byte[1024];
        while ((len = inputStream.read(bytes)) != -1) {
            System.out.println("content : " + new String(bytes, 0, len));
        }
        return "{\"result\":\"success\"}";
    }
    (第三种,使用SneakyThrows,仅仅抛出FileNotFoundException,因为read还有IOException,所以你运行是报错的)
    @GetMapping("/users/test")
    // 使用了SneakyThrows
    // 预期会有异常抛出逻辑
    @SneakyThrows(value = {FileNotFoundException.class})
    public String example() {
        File file = new File("test.txt");
        // 使用了Cleanup
        // 预期会有关闭流操作
        @Cleanup InputStream inputStream = new FileInputStream(file);
        int len;
        byte[] bytes = new byte[1024];
        while ((len = inputStream.read(bytes)) != -1) {
            System.out.println("content : " + new String(bytes, 0, len));
        }
        return "{\"result\":\"success\"}";
    }
}

编译文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

其次看 UserController接口,可以看到反编译的源码有输入流的关闭操作,异常的抛出逻辑。与预期的相同

public class UserController
{

    public UserController()
    {
    }

    public String example()
    {
        InputStream inputStream;
        File file = new File("test.txt");
        inputStream = new FileInputStream(file);
        String s;
        byte bytes[] = new byte[1024];
        int len;
        while((len = inputStream.read(bytes)) != -1) 
            System.out.println((new StringBuilder()).append("content : ").append(new String(bytes, 0, len)).toString());
        s = "{\"result\":\"success\"}";
        // @Cleanup 起的作用,流关闭
        if(Collections.singletonList(inputStream).get(0) != null)
            inputStream.close();
        return s;
        // 异常抛出
        Exception exception;
        exception;
        if(Collections.singletonList(inputStream).get(0) != null)
            inputStream.close();
        throw exception;
        IOException $ex;
        $ex;
        throw $ex;
    }
}

(6)Builder

  • 预期会生成对应的ExampleBuilderBuilder静态类,并且里面有build方法
  • 预期会生成一个builder方法
// 使用了 Builder
// 预期:会生成对应的ExampleBuilderBuilder静态类和builder方法
@Builder
public class ExampleBuilder {

    private final String PUBLIC_NAME = "hahaha";

    private Integer id;

    private String name;

    private String sex;
}

编译两个文件生成class文件,然后我们来反编译一下获取到源码,来看看有啥不同。如果不知道如何反编译,可以查看另外一篇文章:JAVA 如何将class文件转换成java文件

可以从反编译后的源码看到:

  • ExampleBuilder生成了对应的ExampleBuilderBuilder静态类,并且里面有build方法,符合预期
  • ExampleBuilder生成了builder方法,调用静态内部类进行实例的获取,符合预期
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ExampleBuilder.java

package com.study.pojo;


public class ExampleBuilder
{
    public static class ExampleBuilderBuilder
    {

        public ExampleBuilderBuilder id(Integer id)
        {
            this.id = id;
            return this;
        }

        public ExampleBuilderBuilder name(String name)
        {
            this.name = name;
            return this;
        }

        public ExampleBuilderBuilder sex(String sex)
        {
            this.sex = sex;
            return this;
        }
		// 生成对应的pojo实例
        public ExampleBuilder build()
        {
            return new ExampleBuilder(id, name, sex);
        }

        public String toString()
        {
            return (new StringBuilder()).append("ExampleBuilder.ExampleBuilderBuilder(id=").append(id).append(", name=").append(name).append(", sex=").append(sex).append(")").toString();
        }

        private Integer id;
        private String name;
        private String sex;

        ExampleBuilderBuilder()
        {
        }
    }


    ExampleBuilder(Integer id, String name, String sex)
    {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
	// builder方法,调用静态内部类
    public static ExampleBuilderBuilder builder()
    {
        return new ExampleBuilderBuilder();
    }

    private final String PUBLIC_NAME = "hahaha";
    private Integer id;
    private String name;
    private String sex;
}

如果我们使用toBuilder属性,比上面会多一个**toBuilder()**方法,用来生成新实例

// 使用了 Builder
// 预期:会生成对应的ExampleBuilderBuilder构造类和builder方法和生成新实例的toBuilder方法
@Builder(toBuilder = true)
class ExampleBuilderWithToBuilder {

    private final String PUBLIC_NAME = "hahaha";

    private Integer id;

    private String name;

    private String sex;
}

反编译后的代码:多了toBuilder方法,符合预期

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ExampleBuilder.java

package com.study.pojo;


class ExampleBuilderWithToBuilder
{
    // builder方法
    public static ExampleBuilderWithToBuilderBuilder builder()
    {
        return new ExampleBuilderWithToBuilderBuilder();
    }
	// toBuilder方法
    public ExampleBuilderWithToBuilderBuilder toBuilder()
    {
        return (new ExampleBuilderWithToBuilderBuilder()).id(id).name(name).sex(sex);
    }
    
    public static class ExampleBuilderWithToBuilderBuilder
    {

        public ExampleBuilderWithToBuilderBuilder id(Integer id)
        {
            this.id = id;
            return this;
        }

        public ExampleBuilderWithToBuilderBuilder name(String name)
        {
            this.name = name;
            return this;
        }

        public ExampleBuilderWithToBuilderBuilder sex(String sex)
        {
            this.sex = sex;
            return this;
        }

        public ExampleBuilderWithToBuilder build()
        {
            return new ExampleBuilderWithToBuilder(id, name, sex);
        }

        public String toString()
        {
            return (new StringBuilder()).append("ExampleBuilderWithToBuilder.ExampleBuilderWithToBuilderBuilder(id=").append(id).append(", name=").append(name).append(", sex=").append(sex).append(")").toString();
        }

        private Integer id;
        private String name;
        private String sex;

        ExampleBuilderWithToBuilderBuilder()
        {
        }
    }


    ExampleBuilderWithToBuilder(Integer id, String name, String sex)
    {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
	

    private final String PUBLIC_NAME = "hahaha";
    private Integer id;
    private String name;
    private String sex;
}

建造者模式的使用好处是,我们不需要用一堆set来设置属性,极大的方便了代码,举例:

ExampleBuilderWithToBuilder exampleBuilderWithToBuilder1 = ExampleBuilderWithToBuilder.builder().id(1).name("haha").sex("男").build();

ExampleBuilderWithToBuilder exampleBuilderWithToBuilder2 = ExampleBuilderWithToBuilder.builder().id(1).name("ha").sex("男").build();

ExampleBuilderWithToBuilder exampleBuilderWithToBuilder = exampleBuilderWithToBuilder1.toBuilder().name("ha").sex("男").build();

4、总结

(1)优点

  • 方便代码的编写,提高了开发效率。
  • 简洁代码,可更多的关注业务逻辑
  • 方便维护。pojo字段变了,也不用去修改对应的一些属性和方法

(2)缺点

  • 建议少用@Data。因为不能自定义一些属性
  • 参数构造器不能够重载了,要么无参、要么全参
  • 降低了可读性,不会这个知识的可能读不懂
  • 可能存在循环依赖问题,A引用B,B引用A在使用EqualsAndHashCode的时候就会出现循环依赖问题(可以试试),要注意要exclude属性去除可能存在的循环依赖问题
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值