前言
一、引入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() {
}
}