文章目录
作用
以 Java 注解的方式,减少 Java 中的样板代码,如 set/get 方法
安装
- 在 IDE 中安装 lombok 插件,目前常用的 Java IDE 都支持 Lombok 插件,如 IntelliJ IDEA、Eclipse、Netbeans、vs code 等。因为 Lombok 需要在代码编译时,根据相应的注解,生成相应的方法(如 根据 @Getter 注解生成 getter() 方法),而 IDE 需要使用 lombok 插件来提供对 lombok 的支持。
- 需要引入 lombok 的依赖,因为要在代码中使用注解。
maven 方式引入 lombok 依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
常用注解
@NonNull
在lombok v0.11.10中引入。
作用: 生成对参数的空指针检查语句。
使用位置: 可以用在方法或构造函数的参数上。
通过 lombok 生成的方法或构造函数(例如,通过 @Data
注解),都会为参数加上 @NonNull
注解,即为参数添加空指针检查。
@NonNull
生成的代码实例:
if (param == null) throw new NullPointerException("参数名称 is marked @NonNull but is null");
生成的空指针检查,会放到普通方法体的最顶端,放在构造函数的 this()/super()
方法下面
如果顶部已经存在空检查,则不会生成其他空检查。
使用 lombok 的代码:
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
对应的 java 代码:
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked @NonNull but is null");
}
this.name = person.getName();
}
}
@Getter/@Setter
作用: 生成默认的 getter/setter 方法。
使用位置:
- 放在属性上,为这个属性生成 getter/setter 方法
- 放在类上,为这个类中的所有非静态(static)属性生成 getter/setter 方法
对于字段 foo
,生成的 getter/setter 方法名:
- 生成的 getter 方法,如果
foo
为boolean
,则名称为isFoo
,其他类型生成的方法名为getFoo
- 生成的 setter 方法,名称为
getFoo
getter/setter 方法的可见性:
- 默认为
public
- 可以在注解中使用
AccessLevel
来控制,合法的可见级别包括:PUBLIC
,PROTECTED
,PACKAGE
,和PRIVATE
。 - 可以在属性上使用
AccessLevel.NONE
来手动禁用 getter/setter 方法的生成,即覆盖在类上使用的@Getter
,@Setter
或@Data
的行为
使用 lombok 的代码:
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
对应的 java 代码:
public class GetterSetterExample {
private int age = 10;
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
protected void setName(String name) {
this.name = name;
}
}
lombok v1.12.0: 开始支持一些 javadoc 相关的注释,可以查看官方文档
@ToString
作用: 生成 toString()
方法。默认按顺序打印类名以及逗号分隔的属性值。(不包含属性名)
使用位置: 类上
在 @ToString
中设置 includeFieldNames=true
可以在打印结果中包含属性名。
控制打印哪些属性:
- 默认打印所有属性(非static、非final)
- 可以在字段上使用
@ToString.Exclude
,来排除属性,即不打印该属性 - 也可以配合使用:在类上,
@ToString(onlyExplicitlyIncluded = true)
,同时在字段上使用@ToString.Include
,来精确控制打印哪些属性。 - 在
@ToString
中设置callSuper=true
可以调用出父类的toString
方法。Object
类的toString
方法意义不大,所以在继承其他类时在使用。 - 在非静态无参方法上使用
@ToString.Include
,可以打印该方法的执行结果
控制属性打印顺序:
- 使用
@ToString.Include(rank = -1)
为参数设置等级,等级高的属性首先打印 - 默认等级为 0
- 等级相同的属性,按照在源文件中出现的顺序打印
使用 lombok 的代码:
import lombok.ToString;
@ToString
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
@ToString.Exclude private int id;
public String getName() {
return this.name;
}
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
对应的 java 代码:
import java.util.Arrays;
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public String toString() {
return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
@Override public String toString() {
return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
}
}
@EqualsAndHashCode
作用: 生成 equals(Object other)
和 hashCode()
方法
使用位置: 类上
控制使用哪些属性:
- 默认使用所有非static、非transient 属性来实现
equals(Object other)
和hashCode()
方法 - 可以在字段上使用
@EqualsAndHashCode.Exclude
,来排除属性,即不使用该属性 - 也可以配合使用:在类上,
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
,同时在字段上使用@EqualsAndHashCode.Include
,来精确控制使用哪些属性。 - 在
@EqualsAndHashCode
中设置callSuper=true
可以将父类的equals/hashCode
方法应用到生成的自己的equals/hashCode
方法中。但是要小心使用,如果父类是 Object,那么如果设置了callSuper=true
,对象可能除了与自己相等,与其他对象都不相等
使用 lombok 的代码:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
@EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
private String[] tags;
@EqualsAndHashCode.Exclude private int id;
public String getName() {
return this.name;
}
@EqualsAndHashCode(callSuper=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
对应的 java 代码:
import java.util.Arrays;
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof EqualsAndHashCodeExample)) return false;
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (Double.compare(this.score, other.score) != 0) return false;
if (!Arrays.deepEquals(this.tags, other.tags)) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.score);
result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.tags);
return result;
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
if (!other.canEqual((Object)this)) return false;
if (!super.equals(o)) return false;
if (this.width != other.width) return false;
if (this.height != other.height) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + super.hashCode();
result = (result*PRIME) + this.width;
result = (result*PRIME) + this.height;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Square;
}
}
}
还有一些更高级的特性,参考官方文档
@NoArgsConstructor
, @RequiredArgsConstructor
, @AllArgsConstructor
作用: 生成构造函数
使用位置: 类上
@NoArgsConstructor
- 生成无参构造函数
- 如果不能生成无参构造函数(因为
final
属性),会产生编译错误,除非使用@NoArgsConstructor(force = true)
,使final
属性初始化为0
/false
/null
。 - 对于具有约束的字段(例如@NonNull字段),不会生成检查,因此请注意,通常在这些字段稍后进行正确初始化之前,通常不会满足这些约束。
- 某些 Java 结构,需要无参构造函数(例如 hibernate 相关类或服务提供接口等),这个注解通常与
@Data
或其他构造函数注解一块儿使用。
@RequiredArgsConstructor
- 生成一个带有需要特殊处理的属性为参数的构造函数。
- 标记为
@NonNull
的未初始化的属性,会包含在构造函数的参数中,构造函数中会有对参数非空的检查 - 为初始化的
final
属性,会包含在构造函数中,并且还会生成一个只包含final
属性为参数的构造函数
- 标记为
- 参数的顺序属性在源文件中出现的顺序
@AllArgsConstructor
- 生成包含所有属性为参数的构造函数,不管属性有没有初始化
- 如果属性标记为
@NonNull
,会生成非空检查语句
生成另一种构造方式:
- 原理:生成私有构造方法,以及使用私有构造方法的工具方法
- 开启:设置注解的
staticName
属性,如@RequiredArgsConstructor(staticName="of")
- 此时构造对象,使用这种方式:
MapEntry.of("foo", 5)
,而不是传统的new MapEntry<String, Integer>("foo", 5)
方式 - 三个构造函数注解,都支持这个属性
更多高级用法,参考官方文档
使用 lombok 的代码:
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}
对应的 java 代码:
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
private ConstructorExample(T description) {
if (description == null) throw new NullPointerException("description");
this.description = description;
}
public static <T> ConstructorExample<T> of(T description) {
return new ConstructorExample<T>(description);
}
@java.beans.ConstructorProperties({"x", "y", "description"})
protected ConstructorExample(int x, int y, T description) {
if (description == null) throw new NullPointerException("description");
this.x = x;
this.y = y;
this.description = description;
}
public static class NoArgsExample {
@NonNull private String field;
public NoArgsExample() {
}
}
}
@Data
作用: 集合了@ToString
,@EqualsAndHashCode
,所有属性上设置 @Getter
,所有非 final
属性上设置了 @Setter
和 @RequiredArgsConstructor
使用位置: 类上
@Data
可以认为是集合多个注解的快捷方式,但是,它缺少一些细粒度的配置,如 callSuper
, includeFieldNames
and exclude
等。
不过,@Data
可以与它集合的那些注解同时使用,来进行细粒度的配置
更多细节参考官方文档
使用 lombok 的代码:
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;
@Data public class DataExample {
private final String name;
@Setter(AccessLevel.PACKAGE) private int age;
private double score;
private String[] tags;
@ToString(includeFieldNames=true)
@Data(staticConstructor="of")
public static class Exercise<T> {
private final String name;
private final T value;
}
}
对应的 java 代码:
import java.util.Arrays;
public class DataExample {
private final String name;
private int age;
private double score;
private String[] tags;
public DataExample(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setScore(double score) {
this.score = score;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
public void setTags(String[] tags) {
this.tags = tags;
}
@Override public String toString() {
return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
}
protected boolean canEqual(Object other) {
return other instanceof DataExample;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof DataExample)) return false;
DataExample other = (DataExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.getScore());
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + this.getAge();
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
return result;
}
public static class Exercise<T> {
private final String name;
private final T value;
private Exercise(String name, T value) {
this.name = name;
this.value = value;
}
public static <T> Exercise<T> of(String name, T value) {
return new Exercise<T>(name, value);
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
@Override public String toString() {
return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
}
protected boolean canEqual(Object other) {
return other instanceof Exercise;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Exercise)) return false;
Exercise<?> other = (Exercise<?>) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
return result;
}
}
}
delombok
将使用 lombok
注解的源码文件,转换为拥有注解对应方法的源码文件。
参考官方文档
是否要使用 lombok
为什么有的程序员不推荐使用Lombok! 这篇文章中讲了一些 lombok
的优缺点,主要包括:
优点:减少样板代码,使代码看起来简洁
缺点:
- 强制安装IDE插件
- 代码可读性、可调式性降低
- 有坑(需要了解正在使用的
lombok
注解的特性) - 影响升级
- 影响封装性
使用前还是要做一些评估。