Lombok 稳定版特性

Lombok 是一个流行的 Java 库,它通过注解的方式为开发者提供了一种简化 Java 代码的方法。这里介绍了 Lombok 核心包中提供的特性。关于每个特性的详细介绍参见 Lombok 常用注解 、Lombok 其他特性。

val

val 本身并不是一个类型,它是一种特殊的变量声明方式,用于声明一个不可变的局部变量。

📋 概述

val 可以用作局部变量的类型,而不必实际指定类型。执行此操作时,将从设定的初始值表达式推断类型。局部变量也将被定为 final 变量。此功能仅适用于局部变量和 foreach 循环,并且需要初始化表达式。

val实际上是一种“类型”,并且作为Lombok包中的类真实存在。你必须导入它才能使 val 工作(或使用lombok.val类型)。局部变量使用 val 会触发添加 final 关键字以及通过初始化表达式的类型来确定字段类型。

var

var 允许声明一个其类型由初始化表达式推断的变量。需要注意的是,var 声明的变量不是 final 的,这意味着它们的值在初始化后可以被改变。

📋 概述

var工作方式与 val 完全相同,只是局部变量未标记为final。

字段类型由初始化表达式确定,并且任何进一步的赋值虽然现在是合法的(因为该变量不再是final),但是如果再次赋值的类型不匹配将会导致编译错误。

例如,var x = "Hello"; x = Color.RED;不起作用;x 的类型将被推断为java.lang.String,因此x = Color.RED分配将失败。如果推断出的 x 类型是 java.lang.Object ,则可以正常编译此代码,但这不太合理。

@NonNull

@NonNull 注解会在方法或构造函数的参数上添加非空检查。如果参数已经明确标注了其他非空注解(如 @NotNull),则 @NonNull 不会重复添加非空检查。

📋 概述

@NonNull可以用在组件、方法或构造函数的参数上,Lombok 将据此生成一个非空检查语句。

Lombok 将根据字段上的各种 @NonNull 注解,在生成方法或构造函数时添加非空检查,例如@Data。但是,在参数上使用 Lombok 自己的注解 @lombok.NonNull 会导致在该方法的顶部插入非空检查。

空检查看起来像if (param == null) throw new NullPointerException("param is marked non-null but is null");语句被插入到方法的最顶部。对于构造函数,将在this()/super()调用之后立即插入空检查。

如果已经存在非空判断,则不会生成额外的空检查。

@Cleanup

@Cleanup 注解用于确保在代码执行路径退出当前范围之前,自动清理给定资源。如果资源本身实现了 AutoCloseable 接口,Lombok 会自动调用其 close() 方法进行清理。

📋 概述

@Cleanup 用于确保在代码执行路径退出当前范围之前,自动清理给定资源。可以给任意局部变量添加 @Cleanup 注解,例如:@Cleanup InputStream in = new FileInputStream("some/file");
这表示,在方法退出之前,将调用 in.close()方法。并且该调用将被 try/finally 代码块包裹。

如果想要清理的对象没有close()方法,而是有其他一些无参方法,则可以像这样指定该方法的名称:
@Cleanup("dispose") org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);

@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 单词。

@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,等级较高的成员首先打印,相同等级的成员按照它们在源文件中显示的顺序打印。

@EqualsAndHashCode

@EqualsAndHashCode 注解用于生成 equals 和 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(); }

@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 生成的构造函数有相同的签名),则将发生编译器错误。

@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 可以很好地处理字段的泛型参数。

@Value

@Value 是 @Data 的不可变形式,并且所有字段默认是 private final 的。此外, @Value 注解同样会生成一个全参构造函数,除非类中存在 final 字段。

📋 概述

@Value 是 @Data 的不可变形式:默认情况下,所有字段都是private final的,并且不会生成 setter,类本身也将是 final 的,不可被继承。

一般认为 @Value 注解代表了 final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter,如果类中已经包含需要生成的方法,则不会生成该方法,也不会发出警告。例如,如果需要编写自己的 toString ,则不会发生错误,并且 Lombok 不会生成 toString方法。此外,任何显式构造函数,无论参数列表如何,都意味着 Lombok 不会生成构造函数。如果确实希望 Lombok 生成无参构造函数,则需要添加 @AllArgsConstructor 注解。

如果 ‘@Builder’ 和 ‘@Value’ 都在一个类上,则‘@Builder’生成的构造函数优先级更高。

如果不想字段被 final 修饰,可以在字段上使用@NonFinal注解。

如果不想生成的类被 final 修饰,可以在类上使用@NonFinal注解。

可以显示添加 @ToString/@Getter等注解来覆盖 @Value 注解的默认行为。

@Builder

@Builder 注解用于在类上生成构建器模式的代码,允许通过链式调用的方式构建对象。

📋 概述

使用 Lombok 的 @Builder 注解可以显著减少样板代码,并使对象构建过程更加清晰和易于管理。

@SneakyThrows

@SneakyThrows 注解用于静默处理 checked exception, @SneakyThrows 注解实际上会在运行时将 checked exception 转换为 unchecked exception。

📋 概述

@SneakyThrows 用于静默处理 checked exception,这样就不用显示在方法上使用 throws 抛出异常了。

Lombok 并不是忽略、包装、替换或以其他方式修改抛出的选中异常;它只是伪造了编译器。在 JVM(class文件)级别上,无论方法是否有异常,都可以抛出异常,Lombok 通过这种方式工作的。

@SneakyThrows 可以用于下面两个场景:

  • 不需要严格处理的接口:例如 Runnable ,在 run() 方法中,无论抛出何种异常,对于调用线程都是无法处理的。捕获已检查的异常并将其包装在某种 RuntimeException 类型中只会掩盖问题的真正原因。
  • 不可能发生的异常:例如 new String(someByteArray, "UTF-8");声明它可以抛出一个 UnsupportedEncodingException。但根据 JVM 规范,UTF-8 是始终可用的,也就是不会出现这个异常,也就不需要处理它。

其实还有一种情形也适用:使用 lambda 语法 (arg -> action) 时,不需要处理某些异常。不过 Lambda 表达式无法添加注解,因此没法使用了。

@SneakyThrows 注解可以传入指定的异常,如果不给参数,则可以是任意的异常。

@Synchronized

@Synchronized 注解锁定在名为 $lock 的字段上。

📋 概述

@Synchronized 可以让你更为安全地实现 synchronized 关键字所需要的功能,可以让你不暴露锁。与 synchronized 一样,注解只能用于静态方法和实例方法。它的操作与 synchronized 关键字类似,但它锁定不同的对象。关键字锁定在 this 上,但注解锁定在名为 $lock 的字段上,该字段是私有的。

如果该字段不存在,则会创建一个。如果在 static 方法添加该注解,则会锁定名为 $LOCK 的静态字段。

如果需要,你可以自己创建这些锁。如果您已经自己创建了 $lock$LOCK 字段,则不会生成它们。您还可以选择锁定另一个字段,方法是将其指定为注解 @Synchronized 的参数。在此用法变体中,不会自动创建字段,你必须自己显式创建它们,否则将发出错误。

锁定 this 或者当前对象可能产生一些意外的情况,因为不受控制的其他代码也会锁定这些对象,这可能会导致争用条件和其他令人讨厌的线程相关的 bug。

如果更喜欢 java.util.concurrent.locks 样式锁(如果使用的是虚拟线程,则建议使用),可以查看 @Locked 。

@Locked

@Locked 注解使用 java.util.concurrent.locks.ReentrantLock,@Locked 注解可以提高性能,因为它允许多个线程同时读取。

📋 概述

@Locked 将方法中的所有代码包装到一个 java.util.concurrent.locks.ReentrantLock 块中,并在退出该方法时将其解锁。这很像 @Synchronized。

你可以指定一个 ReentrantLock 类型的字段,Lombok 在该字段上加锁。否则,会对静态方法生成 $LOCK、示例方法生成 $lock 字段来加锁。要是字段不存在,会自动创建。

还有 @Locked.Read 和 @Locked.Write 注解,它们使用java.util.concurrent.locks.ReadWriteLock 具体是 ReentrantReadWriteLock,@Locked.Read 用在方法上表示读锁,@Locked.Write 用在方法上表示写锁。

如果使用了 Java 20 引入的虚拟线程,则建议使用该注解而不是 @Synchronized。

@With

在克隆对象的同时修改【不可变】字段值, @With 注解生成的方法会返回一个新的对象实例。

📋 概述

对于需要修改不可变字段的情形,可以通过克隆新的对象并给字段设置新值的方式实现。通过 @With 注解,可以生成 withFieldName(newValue) 方法来生成克隆的对象。

例如,如果创建 public class Point { private final int x, y; } ,则 setter 没有意义,因为字段是final字段。 @With 可以为您生成一个 withX(int newXValue) 方法,该方法将返回一个新的 Point,该Point的 x 和 y 值相同。

@With 依赖于全参构造器,如果此构造函数不存在,则 @With 注解将导致编译时错误。你可以使用 Lombok 自己的 @AllArgsConstructor 自动生成一个全参构造函数。当然,如果你手动编写这个构造函数,这也是可以接受的。

像 @Setter 一样,你可以指定一个访问级别,如果你希望生成的 with 方法不是 public:@With(level = AccessLevel.PROTECTED),也像 @Setter 一样,你还可以在class上添加 @With 注释,这意味着为每个字段(甚至是非 final 字段)生成一个 with 方法。

若要对生成的方法添加注解,可以使用 onMethod=@__({@AnnotationsHere})。不过要小心!这是一项实验性功能。

字段上的 javadoc 会被复制到 with 方法上,@param 会被移动到 with 方法上,移动的意思是:从字段的 javadoc 中删除。如果想单独设置内容,可以在独立的一行中添加被至少两个短横包裹的 WITH 单词,其后的内容会被复制到 with 方法上。

@Getter(lazy=true)

@Getter(lazy=true) 注解会缓存字段值,这种方法适用于初始化代价高昂的字段。

📋 概述

你可以让 Lombok 生成一个 getter 方法,该 getter 将在第一次调用此 getter 时计算一次值,并从那时起缓存它。如果计算该值占用大量 CPU,或者该值占用大量内存,则此值非常有用。要使用此功能,请创建一个 private final 变量,使用运行成本高昂的表达式对其进行初始化,然后使用 @Getter(lazy=true)。该字段将对代码的其余部分隐藏,并且在首次调用 getter 时,对表达式的计算次数不会超过一次。即使昂贵的计算结果是 null ,结果也会被缓存,并且昂贵的计算不需要线程安全,因为 Lombok 负责加锁。

如果初始化表达式很复杂,或者包含泛型,则建议将代码移动到私有(如果可能静态)方法,并改为调用该方法。

@Log

@Log 注解系列用于生成日志对象,支持多种日志框架。

📋 概述

在 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”) 。

📝总结

使用 Lombok 时,需要注意其注解的具体用法和配置选项,以确保生成的代码符合预期。同时,由于 Lombok 生成的是编译时代码,因此在团队协作和代码维护中需要考虑到 Lombok 的使用对其他开发者的影响。

转载请注明出处:码谱记录 » Lombok 稳定版特性