Lombok 是一个流行的 Java 库,它通过注解的方式为开发者提供了一种简化 Java 代码的方法。它提供了多种注解,用于生成常见的样板代码,如 getter/setter 方法、构造方法、toString 方法、equals 和 hashCode 方法等。使用 Lombok 可以显著减少代码量,提高开发效率。
@NonNull
@NonNull 注解会在方法或构造函数的参数上添加非空检查。如果参数已经明确标注了其他非空注解(如 @NotNull),则 @NonNull 不会重复添加非空检查。
🏷 版本
@NonNull在 Lombok v0.11.10 中引入。
📋 概述
@NonNull可以用在组件、方法或构造函数的参数上,Lombok 将据此生成一个非空检查语句。
Lombok 将根据字段上的各种 @NonNull 注解,在生成方法或构造函数时添加非空检查,例如@Data。但是,在参数上使用 Lombok 自己的注解 @lombok.NonNull
会导致在该方法的顶部插入非空检查。
空检查看起来像if (param == null) throw new NullPointerException("param is marked non-null but is null");
语句被插入到方法的最顶部。对于构造函数,将在this()/super()调用之后立即插入空检查。
如果已经存在非空判断,则不会生成额外的空检查。
原生写法
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked non-null but is null");
}
this.name = person.getName();
}
}
Lombok 简化
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
🛠 配置
lombok.nonNull.exceptionType= [ NullPointerException| IllegalArgumentException| JDK| Guava| Assertion]
默认:NullPointerException。当 Lombok 生成 null-check 判断语句时,默认情况下java.lang.NullPointerException会抛出“field name is located non-null but is null”的异常消息。但是,您可以在这里配置为 IllegalArgumentException,让 Lombok 使用此消息抛出该异常。如果配置为 Assertion,assert将生成具有相同信息的语句。如果配置为JDK,则提示消息格式为 java.util.Objects.requireNonNull([field name here], "[field name here] is marked non-null but is null");
。如果配置为Guava,则提示消息格式为 com.google.common.base.Preconditions.checkNotNull([field name here], "[field name here] is marked non-null but is null");
。
lombok.nonNull.flagUsage= [ warning| error]
(默认:未设置)
默认:未设置。如果配置的话,Lombok 会将使用@NonNull
标记为警告或错误。
🔔 说明
Lombok 对已经存在的空检查的检测方案包括扫描与 Lombok 自己的类似的 if 语句或 assert 语句:if 语句的“then”部分包含“throws”语句,无论是否在大括号中;对任何命名为 requireNonNull
或checkNotNull
的方法。 if 语句的条件必须与PARAMNAME == null
完全相同。 assert 语句必须与 PARAMNAME != null
完全相同。requireNonNull方法的调用必须是独占的(仅被该方法调用),或者必须是赋值或变量声明语句的表达式。方法中第一个语句不满足上面的条件,就认为当前方法不存在非空检查,即 Lombok 自动生成非空检查。
基本数据类型的参数上的 @NonNull 会导致警告,不会生成空检查。
在之前,@NonNull用于抽象方法的参数上会生成警告;从版本 1.16.8 开始,情况不再如此,@NonNull也具有记录作用的概念。出于同样的原因,也可以在方法上添加 @NonNull注解,这是允许的,不会生成警告,并且不会生成任何代码。
@Getter/@Setter
用于字段生成 getter/setter 方法。
📋 概述
可以在任何字段上使用 @Getter/@Setter
用于生成 getter/setter 方法。
假如有一个字段名称为 foo
,默认情况下将生成方法名 getFoo
的 getter 方法(如果字段类型为 boolean 则生成 isFoo
),方法名为 setFoo
的 setter 方法,其返回值为 void,并且包含一个与字段类型相同的参数。
默认情况下生成的方法访问修饰符为 public
,可以通过 AccessLevel
修改为 PUBLIC 、 PROTECTED 、 PACKAGE 和 PRIVATE 。
也可以在类上添加 @Getter/@Setter 注解,其作用相当于给该类中的所有非静态字段添加了 @Getter/@Setter 注解。此时,可以通过 AccessLevel.NONE
来禁止某个字段生成 getter/setter 方法。
如果想在生成的方法上添加注解,可以使用 onMethod=@__({@AnnotationsHere})
,如果想给生成的 setter 方法中参数添加注解,可以使用 onParam=@__({@AnnotationsHere})
。不过,该功能目前为实验室功能。
如果在字段上有 javadoc,将会被复制到 getter/setter 方法上:@return
中的内容会被复制到 getter 方法上,@param
中的内容会被复制到 setter 上。还可以定制 setter 方法上的注释内容:在独立的一行中,用超过两个短横线 -
包裹 SETTER
单词。定制 getter 方法上的注释内容:在独立的一行中,用超过两个短横线 -
包裹 GETTER
单词。
原生写法
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*/
private int age = 10;
/**
* Name of the person.
*/
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
/**
* Age of the person. Water is wet.
*
* @return The current value of this person's age. Circles are round.
*/
public int getAge() {
return age;
}
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
*/
public void setAge(int age) {
this.age = age;
}
/**
* Changes the name of this person.
*
* @param name The new value.
*/
protected void setName(String name) {
this.name = name;
}
}
Lombok 简化
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
* @return The current value of this person's age. Circles are round.
*/
@Getter @Setter private int age = 10;
/**
* Name of the person.
* -- SETTER --
* Changes the name of this person.
*
* @param name The new value.
*/
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
🛠 配置
lombok.accessors.chain = [true | false]
默认:false。如果设置为 true ,生成的 setter 将返回 this (而不是 void)。显式配置 chain 的 @Accessors 注解参数优先于此设置。
lombok.accessors.fluent = [ true | false ]
默认:false。如果设置为 true ,则生成的 getter 和 setter 不会以 get
/is
或 set
开头;这些方法将使用与字段相同的名称(减去前缀)。显式配置 fluent 的 @Accessors 注解参数优先于此设置。
lombok.accessors.prefix += 字段前缀
默认:空列表。这是一个列表属性;可以使用 +=
运算符添加条目。可以使用 -=
运算符删除从父配置文件中继承的前缀。Lombok 将从字段名称中去除任何匹配的字段前缀,以确定要生成的 getter/setter 的名称。例如,如果 m
是此设置中列出的前缀之一,则名为 mFoobar
的字段将生成的 getter 方法名为 getFoobar()
而不是 getMFoobar()
。显式配置 prefix 的 @Accessors 注解参数优先于此设置。
lombok.getter.noIsPrefix = [ true | false ]
默认:false。如果设置为 true ,则为 boolean 字段生成的 getter 将使用 get
前缀而不是默认 is
前缀,并且任何调用 getter 的生成代码(如 @ToString
)也将使用 get
而不是 is
。
lombok.accessors.capitalization = [ basic | beanspec ]
默认:basic。有些字段为一个小写字母后跟一个大写,如uShaped
,默认生成的 getter 方法为 getUShaped
,参数设置为 beanspec
则会生成 getuShaped
。
lombok.setter.flagUsage = [ warning | error ]
默认:未设置。如果配置的话,Lombok 会将使用@Setter
标记为警告或错误。
lombok.getter.flagUsage = [ warning | error ]
默认:未设置。如果配置的话,Lombok 会将使用@Getter
标记为警告或错误。
lombok.copyableAnnotations = [完全限定类型的列表]
默认值:空列表。配置以后,Lombok 会将这些注解复制到 setter 参数和 getter 方法。
🔔 说明
生成方法时,会将字段的第一个字段转换为大写,并添加 get
/is
或 set
前缀。
如果已存在具有相同名称(不区分大小写)和相同参数计数的任何方法,则不会生成任何方法。
对于 boolean 类型字段以 is
开头,生成的 getter 方法将不带任何前缀。
对于 java.lang.Boolean
类型得到的是 get
前缀,而不是 is
前缀。
来自常用库的许多指示非 null 的注解(例如 javax.annotation.Nonnull
),则会导致生成的 setter 中出现显式 null 检查。
常见的非空注解(例如 org.eclipse.jdt.annotation.NonNull
),会自动复制到正确的位置(getter 的方法,setter 的参数)。
使用 AccessLevel.NONE 访问级别不会产生任何结果。它仅与 @Data 或 类范围 @Getter 的 或 @Setter 结合使用时才有用。
@Getter 也可以用于枚举,@Setter 不能。
@ToString
在类上使用 @ToString 注解,可以让 Lombok 生成 toString() 方法。
📋 概述
生成的格式是固定的:类名后跟括号,其中包含以逗号分隔的字段,例如 MyClass(foo=123, bar=234)
。
通过将 includeFieldNames
参数设置为 true,可以让 toString() 方法在字符串表示中包含字段名称。
默认情况下,将打印所有非静态字段。如果要跳过某些字段,可以使用 @ToString.Exclude 注解来排除某些字段。或者,也可以使用 @ToString(onlyExplicitlyIncluded = true)
来准确指定要使用的字段,然后用 @ToString.Include 标记要包含的每个字段。
通过将 callSuper
设置为true,可以将父类实现的 toString 结果包含在输出中。
还可以将方法调用的输出包含在 toString 中,不过只能包含不带参数的实例(非静态)方法。为此,请用 @ToString.Include 标记该方法。
使用 @ToString.Include(name = "some other name")
可以更改成员的名称,也可以使用 @ToString.Include(rank = -1)
更改成员的打印顺序。没有等级的成员被视为等级为 0,等级较高的成员首先打印,相同等级的成员按照它们在源文件中显示的顺序打印。
原生写法
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) + ")";
}
}
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;
}
}
}
🛠 配置
lombok.toString.includeFieldNames = [ true | false ]
默认:true。通常,lombok 会以 fieldName = fieldValue
的形式为每个字段生成 toString 文本。如果此设置设置为 false ,lombok 将省略字段的名称,而只是生成以逗号分隔的所有字段值的列表。如果显式指定了注解参数 includeFieldNames
,则优先于此设置。
lombok.toString.doNotUseGetters = [ true | false ]
默认:false。如果设置为 true ,则lombok在生成 toString 方法时将直接访问字段,而不是使用 getter(如果可用)。如果显式指定了注解参数 doNotUseGetters
,则优先于此设置。
lombok.toString.callSuper = [ call | skip | warn ]
默认:skip。如果设置为 call
,lombok toString 将生成对父类实现的调用。如果设置为 skip
“不生成”,则不会生成父类的调用。如果设置为 warn
也不会生成父类调用,但 Lombok 会生成警告信息。
lombok.toString.onlyExplicitlyIncluded = [true | false]
默认:false。默认所有字段(除非 static,名称以&
开头字段)都将要包含在 toString 中,可使用 @ToString.Exclude 或者 @ToString.Include 选项进行修改。如果设置为 true
,则不包含任何内容,除非显式标有 @ToString.Include。
lombok.toString.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@ToString
标记为警告或错误。
🔔 说明
如果已经存在无参数的 toString(),则不再生成方法,而是发出警告,指出 @ToString 注解没有执行任何操作。
数组是通过 Arrays.deepToString 打印的,这意味着包含自身的数组将产生 StackOverflowError。
如果方法被标记为包含,并且它与字段同名,则该方法将替换该字段的 toString 输出,也就是在输出该字段的地方用方法代替。
在 Lombok 1.16.22 之前,可以使用 @ToString 注解的 of
和 exclude
参数进行包含/排除。这种旧式的包含机制仍然受支持,但将来会被弃用。
在成员上同时使用 @ToString.Exclude
和 @ToString.Include
会生成警告;在这种情况下,该成员将被排除在外。
Lombok 不承诺在不同版本之间保持生成 toString() 方法的输出相同。
默认情况下,将自动排除任何以 $
符号开头的变量。如果一定需要,可以使用 @ToString.Include 注解来包含它们。
如果要包含的字段存在 getter 方法,则调用该 getter 方法而不是使用直接字段引用。可以使用@ToString(doNotUseGetters = true)
强制使用字段而不是 getter 方法。
@ToString 也可用于枚举定义。
如果已通过 lombok.config
的 lombok.addNullAnnotations
配置了非空注解,则方法或返回类型将会添加非空注解。
@EqualsAndHashCode
@EqualsAndHashCode 注解用于生成 equals 和 hashCode 方法。
🏷 版本
Lombok 0.10 中的新功能:除非你的类是 final 并且继承了 java.lang.Object,否则 Lombok 会生成一个 canEqual 方法,这意味着 JPA 代理仍然可以等于它们的基类,但添加新状态的子类不会破坏相等条件。如果需要编写自己的 equals 方法,则在更改 equals 和 hashCode 时应始终重写 canEqual 方法。
Lombok 1.14.0 中的新功能:要对 equals (或 canEqual ) 方法的 other 参数添加注解,可以使用 onParam=@__({@AnnotationsHere})
。不过要小心,这是一项实验性功能。
Lombok 1.18.16 中的新功能:可以通过设置为 cacheStrategy 以外的 CacheStrategy.NEVER
值来缓存生成 hashCode() 的结果。如果可以以任何方式的变动从而导致 hashCode() 更改结果,请不要使用它。
📋 概述
@EqualsAndHashCode 注解用于让 Lombok 生成 equals(Object other) 和 hashCode() 方法的实现。默认情况下,它将使用所有非 static、非 transient 的字段,也可以通过用 @EqualsAndHashCode.Include
或 @EqualsAndHashCode.Exclude
标记类型成员来修改使用的字段。或者,可以通过用 @EqualsAndHashCode.Include
字段或方法并使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true)
来准确指定要使用的字段或方法。
当使用 @EqualsAndHashCode 注解的类继承自另一个类时,就需要注意了。父类中的字段也应该有 equals/hashCode 代码。通过设置为 callSuper=true
,可以在生成的方法中包含父类的 equals 和 hashCode 方法。此时,super.hashCode()的结果将包含在哈希算法中,而对于 equals ,如果父类实现认为它不等于传入的对象,则生成的方法将返回 false。
如果你有一个显式的父类(extends 某个类),你就不得不提供 callSuper 参数来明确你已经考虑过了该情形;否则将出现警告。如果没有显示的父类(extends 某个类),设置参数 callSuper=true 将会导致编译错误。
如果想覆盖某个字段的 equals 行为,可以在字段上添加注解 @EqualsAndHashCode.Include(replaces = “fieldName”)。然后在定义一个方法:@EqualsAndHashCode.Include BigDecimal fieldName() { return fieldName.stripTrailingZeros(); }
原生写法
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;
}
}
}
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;
}
}
}
🛠 配置
lombok.equalsAndHashCode.doNotUseGetters = [true | false]
默认:false。如果设置为 true ,lombok 将直接访问字段,而不是在生成 equals 和 hashCode 方法时使用 getter 方法。如果显式指定了注解参数 doNotUseGetters
,则优先于此设置。
lombok.equalsAndHashCode.callSuper = [call | skip | warn]
默认:warn。如果设置为 call ,lombok 将生成会调用父类实现的 hashCode 和 equals 方法。如果设置为 skip “不生成”,则不会生成此类调用。默认行为类似于 skip ,并带有附加警告。
lombok.equalsAndHashCode.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@EqualsAndHashCode
标记为警告或错误。
🔔 说明
如果一个数组包含自身引用,可能会产生 StackOverflowError 。不过 ArrayList 不会有该问题。
目前,hashCode 的实现在各个版本之间差异不大,不过如果后面出现一种显著提高性能的哈希算法,有可能会在未来替换成该算法。
目前,double 中的 NaN
和 float 中的 NaN
被认为是相等的。
尝试排除 static 字段,或 transient 字段,或不存在的字段将会出现警告。
如果方法被标记为包含,并且该方法与字段具有相同的名称,则该方法将替换该字段。
在 Lombok 1.16.22 之前,可以使用 @EqualsAndHashCode 注释的 of 和 exclude 参数进行包含/排除。这种旧式的包含机制仍然受支持,但将来将被弃用。
默认情况下,将自动排除任何以 $
符号开头的变量,只能通过用 @EqualsAndHashCode.Include
将其手动包含进来。
如果要包含的字段存在 getter 方法,则调用该 getter 方法而不是使用直接字段引用。可以通过参数阻止此行为:@EqualsAndHashCode(doNotUseGetters = true)
@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor
这三个注解都是用来生成构造方法的。
📋 概述
@NoArgsConstructor 将生成一个没有参数的构造函数。如果类中包含 final 修饰的字段,将会编译错误,此时,可以使用 @NoArgsConstructor(force = true)
强制将 final 字段初始化为 0 / false / null 。如果有字段被标记为非空字段(添加了 @NonNull等)将不会被校验,因为无法满足非空条件。某些框架(如 Hibernate /SPI 等)需要无参构造函数,该注解通常与 @Data 注解或其他有参构造的注解结合使用。
@RequiredArgsConstructor 将生成一个有参构造函数:所有未初始化的 final 字段以及任何标记为 @NonNull 未初始化的字段都会获得一个参数。对于标有 @NonNull 的字段,还会生成显式 null 检查,如果任何参数用于标记为 @NonNull 的字段为 null,则构造函数将抛出NullPointerException。参数的顺序与字段在类中的出现顺序相同。
@AllArgsConstructor 将生成一个全参构造函数。标有 @NonNull 的字段将触发对这些参数进行 null 检查。
这三个注解都可以使用别名来简写,此时,生成的构造方法将是私有 private 的,外界可以通过一个静态方法访问内部私有的构造函数。该功能通过参数 staticName
启用:例如有一个这样的注解 @RequiredArgsConstructor(staticName="of")
,意味着可以使用 MapEntry.of("foo", 5)
初始化对象,而不是更长的 new MapEntry<String, Integer>("foo", 5)
。
要在生成的构造函数上添加注释,可以使用 onConstructor=@__({@AnnotationsHere}) ,但要小心,这是一个实验性功能。
这三个注解会跳过静态字段。
如果已经自己写好了构造函数,上面的注解还是会根据自身功能生成相应的构造函数。如果发生冲突(自己定义的构造函数与 Lombok 生成的构造函数有相同的签名),则将发生编译器错误。
原生写法
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() {
}
}
}
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;
}
}
🛠 配置
lombok.allArgsConstructor.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@AllArgsConstructor
标记为警告或错误。
lombok.requiredArgsConstructor.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@RequiredArgsConstructor
标记为警告或错误。
lombok.noArgsConstructor.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@NoArgsConstructor
标记为警告或错误。
lombok.anyConstructor.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用上述三个注解中任意一个都标记为警告或错误。
lombok.anyConstructor.addConstructorProperties = [true | false]
默认:false。如果设置为 true,则 Lombok 会将 @java.beans.ConstructorProperties
添加到生成的构造函数中。
lombok.copyableAnnotations = [完全限定类型的列表]
Lombok 将会把该列表中指定的注解复制到构造器的参数、Setter 参数、getter 方法上。
lombok.noArgsConstructor.extraPrivate = [true | false]
默认:false。如果 true ,lombok 将为所有带 @Value 或 @Data 注解的类生成一个私有的无参构造函数,该构造函数将所有字段设置为默认值 (null / 0 / false)。
🔔 说明
即使字段显式初始化为 null
,lombok 也不会将 null 视为满足要求,并且不会将该字段视为“必需”参数。
无参构造函数不能添加 @java.beans.ConstructorProperties
注解,这也解释了为什么 @NoArgsConstructor 缺少 suppressConstructorProperties
参数。生成的静态工厂方法也不会得到 @ConstructorProperties ,因为这个注解只能添加到真正的构造函数中。
这三个注解可以用于枚举。生成的构造函数将始终是私有的,因为非私有构造函数在枚举中是不合法的。您不必指定 AccessLevel.PRIVATE
。
如果构造函数是由 @Data
,@Value
注解生成的,则 flagUsage
参数将不生效。
@Data
@Data 注解包含了 @ToString,@EqualsAndHashCode,@Getter,@Setter,@RequiredArgsConstructor。此外, @Data 注解同时会生成一个无参构造函数,除非类中存在 final 字段。
📋 概述
@Data 是一个组合注解,它包含了 @ToString
, @EqualsAndHashCode
,@Getter
,@Setter
,@RequiredArgsConstructor
。添加了 @Data 注解,意味着该类所有字段包含了 getter/setter方法、toString方法、equals方法、hashCode 方法,以及初始化所有非空字段的构造函数(如果有final 字段会初始化)。
@Data 注解虽然包含了多个注解的特征,但是这些注解的参数(如 callSupper、exclude等)确不能在 @Data 中设置,如果想设置这些参数,需要显示添加特定注解。
所有生成的 getter 和 setter 都将是 public 。若要覆盖访问级别,请使用显式设置 @Setter 或 @Getter。
生成的hashCode 和 equals 方法将不考虑所有标记为transient的字段,所有静态字段都将被完全跳过。
如果将要生成方法在类中已经显示存在,则不会生成该方法,并且不会发出警告或错误。例如,如果您已经有一个签名为 equals(AnyType param)
的方法,则不会生成任何 equals
方法。
@Data 可以很好地处理字段的泛型参数。
原生写法
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;
}
}
}
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;
}
}
🛠 配置
lombok.data.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@Data
标记为警告或错误。
lombok.noArgsConstructor.extraPrivate = [true | false]
默认:false。如果设置为 true,lombok 将为任何 @Data 带注释的类生成一个私有的无参构造函数,该构造函数将所有字段设置为默认值 (null / 0 / false)。
🔔 说明
生成方法的非空检查将参照 @Getter/@Setter 注解。
默认情况下,将自动排除任何以 $
符号开头的变量。您可以通过指定显式注释(例如 @Getter 或 @ToString )并使用‘of’参数来包含它们。
@Builder
@Builder 注解用于在类上生成构建器模式的代码,允许通过链式调用的方式构建对象。
📋 概述
使用 Lombok 的 @Builder 注解可以显著减少样板代码,并使对象构建过程更加清晰和易于管理。
@Builder 相对来说会复杂些,另起一篇详细介绍。
@Log
@Log 注解系列用于生成日志对象,支持多种日志框架。
🏷 版本
Lombok v0.10 中添加了各种 @Log 变体。
Lombok v1.16.24 中的新功能:添加了谷歌的 FluentLogger(通过 @Flogger )。
Lombok v1.18.10 中的新功能: @CustomLog 添加它允许自定义 logger。
📋 概述
在 class 上添加 @Log 注解,可以获得一个 static final log 字段,该字段将按照指定的日志框架进行初始化。
@Log 注解是一系列注解的统称,它们包括:
- @CommonsLog:
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
- @Flogger:
private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
- @JBossLog:
private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
- @Log:
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
- @Log4j:
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
- @Log4j2:
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
- @Slf4j:
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
- @XSlf4j:
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
- @CustomLog:
private static final com.foo.your.Logger log = com.foo.your.LoggerFactory.createYourLogger(LogExample.class);
@CustomLog 主要用来支持公司私有的日志框架。使用前,需要在 lombok.config 中配置。
例如,配置为 lombok.log.custom.declaration = com.foo.your.Logger com.foo.your.LoggerFactory.createYourLog(TYPE)(TOPIC)
。在等号右边首先定义日志类型,然后是一个空格,接着是日志工厂,日志实现方法名,后面是参数。参数包括: TYPE (将此 @Log 修饰类型以class形式传递)、 NAME (传递此 @Log 修饰类型的完全限定名称)、 TOPIC (传递@CustomLog注解的 topic 参数字符串)和 NULL (传递 null )。
日志类型是可选的,如果省略,则使用日志工厂类型。
默认情况下,日志的主题(或名称)将是使用 @Log 注解的类名称。也可以通过指定 topic 参数来自定义。例如: @XSlf4j(topic=”reporting”) 。
原生写法
public class LogExample {
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
public static void main(String... args) {
log.severe("Something's wrong here");
}
}
public class LogExampleOther {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
public static void main(String... args) {
log.error("Something else is wrong here");
}
}
public class LogExampleCategory {
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog("CounterLog");
public static void main(String... args) {
log.error("Calling the 'CounterLog' with a message");
}
}
Lombok 简化
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
@Log
public class LogExample {
public static void main(String... args) {
log.severe("Something's wrong here");
}
}
@Slf4j
public class LogExampleOther {
public static void main(String... args) {
log.error("Something else is wrong here");
}
}
@CommonsLog(topic="CounterLog")
public class LogExampleCategory {
public static void main(String... args) {
log.error("Calling the 'CounterLog' with a message");
}
}
🛠 配置
lombok.log.fieldName = 标识符
默认值:log。默认情况下,生成的日志字段名为“log”,可以使用此设置将其更改为其他名称。
lombok.log.fieldIsStatic = [ true | false ]
默认值:true。通常,生成的 log 字段是 static 字段。通过将此键设置为 false ,生成的字段将改为实例字段。
lombok.log.custom.declaration = LoggerType LoggerFactoryType.loggerFactoryMethod(loggerFactoryMethodParams)(loggerFactoryMethodParams)
当使用 @CustomLog 时需要配置的内容。
lombok.log.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用各种@Log
标记为警告或错误。这里 @Log 是统称,指的是所有的日志注解。
lombok.log.custom.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@CustomLog
标记为警告或错误。
lombok.log.apacheCommons.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@CommonsLog
标记为警告或错误。
lombok.log.flogger.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@Flogger
标记为警告或错误。
lombok.log.jbosslog.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@JBossLog
标记为警告或错误。
lombok.log.log4j.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@Log4j
标记为警告或错误。
lombok.log.log4j2.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@Log4j2
标记为警告或错误。
lombok.log.slf4j.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@Slf4j
标记为警告或错误。
lombok.log.xslf4j.flagUsage = [warning | error]
默认:未设置。如果配置的话,Lombok 会将使用@XSlf4j
标记为警告或错误。
🔔 说明
如果已存在一个 log 字段,则会发出警告,并且不会生成任何代码。
📝总结
使用 Lombok 时,需要注意其注解的具体用法和配置选项,以确保生成的代码符合预期。同时,由于 Lombok 生成的是编译时代码,因此在团队协作和代码维护中需要考虑到 Lombok 的使用对其他开发者的影响。