lombok详解

前言

一、引入lombok

此处笔者使用1.16.18版本举例分析。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.18</version>
</dependency>

二、Lombok注解

@NonNull注解

1.空指针排查

// 原始类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";
    public User(@NonNull String name) {
        this.name = name;
    }
}

// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";
    public User(@NonNull String name) {
        if (name == null) { // 校验 @NonNull 修饰的属性是否为空
            throw new NullPointerException("name");
        } else {
            this.name = name;
        }
    }
}

2.配合@RequiredArgsConstructor使用

详情见下方@RequiredArgsConstructor注解

// 原始类
@RequiredArgsConstructor
public class User {

    private String name;

    @NonNull
    private Integer age;

    private final String type = "type";
}

// 编译后的类
public class User {
    private String name;
    @NonNull
    private Integer age;
    private final String type = "type";

    @ConstructorProperties({"age"}) // 显示该构造函数的参数与getter方法相对应
    public User(@NonNull Integer age) {
        if (age == null) { // 校验 @NonNull 修饰的属性是否为空
            throw new NullPointerException("age");
        } else {
            this.age = age;
        }
    }
}

3.配合@Setter使用

当放在setter方法的字段上,将生成一个空检查,如果为空,则抛出NullPointerException。
注意:作用于final修饰且已进行初始化的属性,无效果(final修饰的属性不能再次赋值),示例代码:

// 原始类
@Setter
public class User {

    private String name;

    @NonNull
    private Integer age;

    @NonNull
    private final String type = null;
}

// 编译后的类
public class User {
    private String name;
    @NonNull
    private Integer age;
    @NonNull
    private final String type = null;

    public User() {
    }

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

    public void setAge(@NonNull Integer age) {
        if (age == null) {
            throw new NullPointerException("age");
        } else {
            this.age = age;
        }
    }
}

@Getter/@Setter注解

在JavaBean或类JavaBean中使用,使用此注解会生成对应的getter方法和setter方法

1.final变量

对于类中的final变量,@Setter不会生成setter方法;
提示:final变量的特性(final属性或者变量一旦赋值之后就不可修改);

// 原始类
@Getter
@Setter
public class User {
    private String name;
    private Integer age;
    private final String type = "type";
}

// 反编译的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";
    public User() {
    }
    public String getName() {
        return this.name;
    }
    public Integer getAge() {
        return this.age;
    }
    public String getType() {
        this.getClass();
        return "type";
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

2.value属性

@Getter(AccessLevel.PRIVATE) / @Setter(AccessLevel.PRIVATE),可以指定对象属性对应(get/set)方法的访问范围(详情见下方lombok常见属性 AccessLevel),默认值为AccessLevel.PUBLIC;
示例代码:
@Getter:

// 原始类
@Getter(AccessLevel.PRIVATE) // <=> @Getter(value = AccessLevel.PRIVATE)
public class User {

    private String name;

    @Getter(value = AccessLevel.PUBLIC) // 作用于单个属性访问范围,优先级高于类注解
    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    private String getName() {
        return this.name;
    }

    private String getType() {
        this.getClass();
        return "type";
    }

    public Integer getAge() {
        return this.age;
    }
}

@Setter:

// 原始类
@Setter // <=> @Setter(value = AccessLevel.PUBLIC)
public class User {

    private String name;
    
    // age属性不生成set方法
    @Setter(AccessLevel.NONE) 
    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

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

@ToString注解

在JavaBean中使用,注解会自动重写对应的toStirng方法:

1.默认效果:

// 原始类
@ToString
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 反编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        StringBuilder var10000 = (new StringBuilder()).append("User(name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

2.“of”属性

此属性指定toString方法包含的对象属性
@ToString(of= “column1”,“column2”)
意义:只生成包含column列所对应的元素的参数的toString方法,即在生成toString方法时只包含column参数;示例代码:

// 原始类
@ToString(of = {"name"})
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 反编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        return "User(name=" + this.name + ")";
    }
}

3.“exclude”属性

此属性指定toString方法不包含的属性
@ToString(exclude={“column1”,“column2”})
意义:排除多个column列所对应的元素;示例代码:

4.“includeFieldNames”属性

此属性控制是否打印属性名;
意义:includeFieldNames = false 时,toString方法不打印属性名,只打印属性值,反之亦然;默认值为true;
@ToString(includeFieldNames = false)
示例代码:

// 原始类
@ToString(includeFieldNames = false)
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 反编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        StringBuilder var10000 = (new StringBuilder()).append("User(").append(this.name).append(", ").append(this.age).append(", ");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

@ToString(includeFieldNames = true)
示例代码:

// 原始类
@ToString(includeFieldNames = true)
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 反编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        StringBuilder var10000 = (new StringBuilder()).append("User(name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

@ToString注解中includeFieldNames的定义:

// 默认打印属性方法名
boolean includeFieldNames() default true;

5.“callSuper”属性

此属性指定是否打印父类信息;
意义:callSuper = false 时,toString方法不打印父类信息,只打印子类属性信息;callSuper = true 时,toString方法打印父类信息,以及子类属性信息;默认值为false;
@ToString(callSuper = true)
示例代码:

// 父类(实际使用中可为父类添加setter,getter或重写toString,效果更佳)
public class BaseEntity {
    private String gender;
}

// 原始类
@ToString(callSuper = true)
public class User extends BaseEntity {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 反编译后的类
public class User extends BaseEntity {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        // 调用父类toString方法打印父类属性信息
        StringBuilder var10000 = (new StringBuilder()).append("User(super=").append(super.toString()).append(", name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

@ToString(callSuper = false)
tips:事实上,当你给它赋值false时,idea可能会有如下图提示,“冗余默认参数值分配”;

这是啥意思嘞,就是我们赋了和该属性默认值相同的值,它本来就是默认false嘛,如下:

// toString注解默认不打印父类信息
boolean callSuper() default false;

看到这里,其实就很好解释这个问题:有些同学在用@ToString时,为什么看不到父类的属性信息;
好了,上示例代码:

// 父类(实际使用中可为父类添加setter,getter或重写toString,效果更佳)
public class BaseEntity {
    private String gender;
}

// 原始类 @ToString(callSuper = false) <=> @ToString
@ToString(callSuper = false)
public class User extends BaseEntity {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 反编译后的类
public class User extends BaseEntity {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        // 调用父类toString方法打印父类属性信息
        StringBuilder var10000 = (new StringBuilder()).append("User(super=").append(super.toString()).append(", name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

5.“doNotUseGetters”属性

该属性指定toString()方法获取属性值时是否不使用getter,注意:是不使用;
意义:doNotUseGetters = false 时,toString方法使用getter获取对象的属性值(当属性存在可用的getter时);doNotUseGetters = true 时,toString方法不使用getter获取对象的属性值(当属性存在可用的getter时);默认值为false,即属性值默认使用getter;
小问题:为什么属性定义这么绕呢,笔者还没想到,欢迎大家评论补充或@穆亚琦 讨论哦!
好了,到了上实例代码的时刻:
@ToString(doNotUseGetters = false):

// 原始类
@ToString(doNotUseGetters = false) // 此处默认值为false,为了明确作用,显式赋值
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String toString() {
        StringBuilder var10000 = (new StringBuilder()).append("User(name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

小伙伴应该发现了细节,doNotUseGetters = false,但是为什么没有使用get方法呢,是代码有问题,什么问题呢,就是因为 没有给 User定义get方法呀,让我们把getter加上再看:

// 原始类
@Getter // 也可用显式的get方法替代,作用(效果)一致
@ToString(doNotUseGetters = false) // 此处默认值为false,为了明确作用,显式赋值
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    public String getType() {
        this.getClass();
        return "type";
    }

    public String toString() {
        return "User(name=" + this.getName() + ", age=" + this.getAge() + ", type=" + this.getType() + ")";
    }
}

看了默认值false的效果后,让我们再来对比一下 doNotUseGetters = true时的情况:
@ToString(doNotUseGetters = true):

// 原始类
@Getter // 这一次没有忘记加入get方法哦
@ToString(doNotUseGetters = true) // 不使用getter获取属性值
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    public String getType() {
        this.getClass();
        return "type";
    }

    public String toString() {
        StringBuilder var10000 = (new StringBuilder()).append("User(name=").append(this.name).append(", age=").append(this.age).append(", type=");
        this.getClass();
        return var10000.append("type").append(")").toString();
    }
}

讲了这么多,有没有实际运用场景,或者容易出现的坑呢?当然有,让我们一起看下常见的空指针问题:
话不多说,代码先上为敬:

// 原始类
@Getter
@ToString // @ToString <=> @ToString(doNotUseGetters = false)
public class User {

    private String name;

    private Integer age;

    private final String type = "type";

    // 自定义getter可覆盖 @Getter 中的对应getter
    public Integer getAge() {
        System.out.println("age is valid : " + (age > 0));
        return age;
    }
}

这段代码有没有bug呢(此处隐藏编译后代码若干行,,,),可能你已经看出来了(下一段可自动跳过,,,),如果没有看出问题所在,别急,请再耐心看下一段代码:

// 测试类
@Test
public void testEntry(){
    User user = new User();
    System.out.println("user : " + user);// println方法默认调用user.toString()
}

真实情况是什么样呢,请看执行后情况:
在这里插入图片描述
为什么结果会这样呢,这就是 getAge() 方法在捣鬼:

public Integer getAge() {
    System.out.println("age is valid : " + (this.age > 0));
    return this.age;
}
// this.age > 0 的执行过程:
{
    int temp = age.intValue(); // 会不会出现空指针呢???
    temp > 0;
}

至此,真相大白,因此大家使用@ToString注解,并且重写getter时,一定要注意doNotUseGetters 属性值。

@NoArgsConstructor注解

很好理解,为JavaBean生成空参构造器,单独使用时貌似并没有什么效果;

// 原始类1 不使用注解
public class User {

    private String name;

    private Integer age;

    private final String type = "type";

}
// 编译后的类1
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }
}
// ----------------------------------------------------------------
// 原始类2 使用空参注解
@NoArgsConstructor
public class User {

    private String name;

    private Integer age;

    private final String type = "type";

}
// 编译后的类2
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }
}

实际上,@NoArgsConstructor 一般结合其他几个构造器注解一起使用,用于提供一个无参构造器;对于具有约束的字段,例如 @NonNull 字段,不会生成任何检查,因此请注意,在稍后正确初始化这些字段之前,通常不会满足这些约束。

1.“force”属性

分析force属性之前,先看一段代码:

很明显,final属性未初始化导致编译错误;跟force属性有什么关系呢,请再看:

force = true时,使用 0 / false / null 初始化所有 final 字段,从而避免了编译错误,下面是编译后的代码:

public class User {
    private String name;
    private Integer age;
    private final String type = null; // String类型的属性初始化为null

    public User() {
    }
}

默认为false,表示是否针对final字段进行特殊处理,如果将其设置为true,则上面的编译错误将会消失,内部处理为,将final字段初始化为0\false\null

2.“access”属性

设置构造器访问范围(详见lombok常见属性 AccessLevel),默认值为AccessLevel.PUBLIC,表示public范围的构造器
示例代码:

// 原始类
// @NoArgsConstructor <=> @NoArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor 
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }
}

// 原始类
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    private User() {
    }
}

3.“staticName”属性

一旦设置该值,则会生成一个静态的“构造器”工厂,其内部包裹着一个私有的构造器,对外提供创建对象的功能,这是明显的工厂模式。
示例代码:

// 原始类
@NoArgsConstructor(staticName = "getInstance")// 默认值为"",不生成工厂方法
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}
// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    private User() {
    }
    // 构造器工厂
    public static User getInstance() {
        return new User();
    }
}

怎么样,是不是省去了书写工厂方法的过程呢…

@RequiredArgsConstructor注解

和@NoArgsConstructor使用方法类似,也是在类上使用,但是这个注解可以生成带参或者不带参的构造方法,

1.有参构造器

带参时,只能是类中所有带有 @NonNull注解的和以final修饰的未经初始化的字段,示例代码:

// 原始类
@RequiredArgsConstructor
public class User {

    private String name;

    @NonNull
    private Integer age;

    private final String type;
}

// 编译后的类
public class User {
    private String name; // 没有@NonNull 或 final 修饰,因此不作为构造器入参
    @NonNull
    private Integer age;
    private final String type;

    @ConstructorProperties({"age", "type"}) // 显示该构造函数的参数与getter方法相对应
    public User(@NonNull Integer age, String type) {
        // @NonNull 作用于 age 属性时,对其进行非空校验
        if (age == null) {
            throw new NullPointerException("age");
        } else {
            this.age = age;
            this.type = type;
        }
    }
}

2.无参构造器

如果针对User类使用@RequiredArgsConstructor注解实现无参构造方法,那么我们就要这样定义实体类:

// 原始类
@RequiredArgsConstructor
public class User {

    private String name;

    private Integer age;

    private final String type = "type"; // 对final属性进行初始化
}

// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    // 没有 @NonNull 修饰、或final修饰的未初始化属性,构造器无入参
    public User() {
    }
}

3.“access”属性

作用同 @NoArgsConstructor.access,默认值同样为 AccessLevel.PUBLIC:
AccessLevel access() default AccessLevel.PUBLIC;

4.“staticName”属性

作用同 @NoArgsConstructor.access,默认值为 “”:
String staticName() default “”;

@AllArgsConstructor注解

@AllArgsConstructor同样是在类上使用,该注解提供一个全参数的构造方法,默认不提供无参构造。需要注意的是,这里的全参不包括已初始化的final字段。

1.final 修饰的属性

由于final修饰的字段,一旦被赋值不允许再被修改,已初始化的final 字段不作为构造器入参,
示例代码:
final修饰,且初始化:

// 原始类
@AllArgsConstructor
public class User {

    private String name;

    private Integer age;
    
    // final修饰,且已初始化
    private final String type = "type";
}

// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    @ConstructorProperties({"name", "age"}) // 显示该构造函数的参数与getter方法相对应
    public User(String name, Integer age) { // 入参不包含 type 属性
        this.name = name;
        this.age = age;
    }
}

final修饰,但是属性未初始化时,该属性可以作为入参:

// 原始类
@AllArgsConstructor
public class User {

    private String name;

    private Integer age;
    // final修饰,未进行初始化
    private final String type;
}

// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type;

    @ConstructorProperties({"name", "age", "type"}) // 显示该构造函数的参数与getter方法相对应
    public User(String name, Integer age, String type) { // 入参不包含 type 属性
        this.name = name;
        this.age = age;
        this.type = type;
    }
}

2.“access”属性

作用同 @NoArgsConstructor.access,默认值为 AccessLevel.PUBLIC:
AccessLevel access() default AccessLevel.PUBLIC;

3.“staticName”属性

作用同 @NoArgsConstructor.access,默认值为 “”:
String staticName() default “”;

@Data注解

该注解使用在类上,是最常用的注解,它结合了@ToString,@EqualsAndHashCode, @Getter和@Setter。
1.默认效果
本质上使用@Data注解,类默认@ToString和@EqualsAndHashCode以及每个字段都有@Setter和@getter。该注解也会生成一个公共无参构造函数。
示例代码:

// 原始类
@Data
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}

// 编译后的类
public class User {
    private String name;
    private Integer age;
    private final String type = "type";

    public User() {
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    public String getType() {
        this.getClass();
        return "type";
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof User)) {
            return false;
        } else {
            User other = (User)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                label47: {
                    Object this$name = this.getName();
                    Object other$name = other.getName();
                    if (this$name == null) {
                        if (other$name == null) {
                            break label47;
                        }
                    } else if (this$name.equals(other$name)) {
                        break label47;
                    }

                    return false;
                }

                Object this$age = this.getAge();
                Object other$age = other.getAge();
                if (this$age == null) {
                    if (other$age != null) {
                        return false;
                    }
                } else if (!this$age.equals(other$age)) {
                    return false;
                }

                Object this$type = this.getType();
                Object other$type = other.getType();
                if (this$type == null) {
                    if (other$type != null) {
                        return false;
                    }
                } else if (!this$type.equals(other$type)) {
                    return false;
                }

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $age = this.getAge();
        result = result * 59 + ($age == null ? 43 : $age.hashCode());
        Object $type = this.getType();
        result = result * 59 + ($type == null ? 43 : $type.hashCode());
        return result;
    }

    public String toString() {
        return "User(name=" + this.getName() + ", age=" + this.getAge() + ", type=" + this.getType() + ")";
    }
}

注意:虽然@Data注解非常有用,但是它没有与其他注解相同的控制粒度。

2.“staticContrustor”属性

@Data提供了一个可以生成静态工厂的单一参数,将staticConstructor参数设置为所需要的名称,Lombok自动生成的构造函数设置为私有,并提供公开的给定名称的静态工厂方法。

// 原始类
@Data(staticConstructor = "getInstance")
public class User {

    private String name;

    private Integer age;

    private final String type = "type";
}


// 编译后的类
public class User {
    ......
    // 私有构造函数
    private User() {
    
    }
    // 静态工厂方法
    public static User getInstance() {
        return new User();
    }
    
    ......
}

@EqualsAndHashCode注解

使用此注解会自动重写对应的equals方法和hashCode方法;

@Value注解

@Builder注解

@Cleanup注解

@Synchronized注解

@SneakyThrows注解

@With注解

@Log注解

@Slf4j注解

在需要打印日志的类中使用,当项目中使用了slf4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可;

@Log4j注解

在需要打印日志的类中使用,当项目中使用了log4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可;
在使用以上注解需要处理参数时,处理方法如下(以@ToString注解为例,其他注解同@ToString注解):
@ToString(exclude=“column”)
意义:排除column列所对应的元素,即在生成toString方法时不包含column参数;
@ToString(exclude={“column1”,“column2”})
意义:排除多个column列所对应的元素,其中间用英文状态下的逗号进行分割,即在生成toString方法时不包含多个column参数;

Constructor 全局配置

三、lombok常见问题

1.基类下转型到子类丢失set,get方法

四、lombok常见属性

1.访问范围 AccessLevel

可理解为Java的访问修饰符:

public enum AccessLevel {
    PUBLIC, // 公共范围, 对应 public
    MODULE, // 
    PROTECTED, // 保护范围, 对应 protected
    PACKAGE, // 包范围, 对应 default
    PRIVATE, // 私有范围, 对应 private
    NONE; // 空,使用Setter,Getter时可不生成对应方法

    private AccessLevel() {
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值