Lombok ——超酷的Java库
Lombok
lombok 是什么
Lombok 是一个超酷的 Java 库,旨在通过减少样板代码来提升开发效率。
主要作用
- 减少样板代码
- 提高代码可读性
- 节省开发时间
- 减少代码出错的机会
常用注解
- @Getter / @Setter
- @ToString
- @EqualsAndHashCode
- @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor
- @Data
- @Builder
- @Value
- @Slf4j
- @SneakyThrows
注意
- 局限性:Lombok基于注解处理器和字节码操作,某些IDE、框架、Java版本升级时可能会导致Lombok无法正常工作,需要额外的配置或更新
- Lombok注解生成的代码不可见,过度什么事可能会导致新成员理解代码逻辑困难
- 代码在调试时不可见,可能会影响对代码执行流程的理解
使用方法
Maven项目 - pom.xml中添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version> <!-- 注意查看最新的 Lombok 版本,并替换此处的版本号 -->
<scope>provided</scope>
</dependency>
**TIP: ** scope设置为provided,表示仅编译时使用Lombok,而在项目打包时,并不会将Lombok依赖打包进去,可减少最终项目Jar 包的体积大小。
使用Gradle - build.gradle文件添加
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.20' // 编译时依赖
annotationProcessor 'org.projectlombok:lombok:1.18.20' // 注解处理
}
部分详解
-
@Getter/@Setter: 自动为字段/类生成getter/setter方法参数
AccessLevel:PUBLIC(默认)、PROTECTED、PACKAGE、PRIVATE、MODULEimport lombok.Getter; import lombok.Setter; import lombok.AccessLevel; public class User { @Getter(AccessLevel.PUBLIC) @Setter(AccessLevel.PROTECTED) private String name; @Getter(AccessLevel.PRIVATE) private int age; } -
@ToString:自动生成toString( )方法参数
exclude:用于排除不希望包含在toString( )方法的字段import lombok.ToString; @ToString(exclude = "password") public class User { private String name; private int age; private String password; }参数
callSuper:是否调用父类的toString( )方法import lombok.ToString; @ToString(callSuper = true) public class Employee extends User { private String position; }参数
onlyExplicitlyIncluded:用于控制仅生成包含@ToString.Include注解的字段(适用于想要手动选择部分字段,而不是默认全部生成的情况) -
@EqualsAndHashCode:用于自动生成equals( )和hashCode( )参数
exclude:用于指定不包含在equals( ) 和 hashCode( )方法中的字段import lombok.EqualsAndHashCode; @EqualsAndHashCode(exclude = "age") public class User { private String name; private int age; }参数
callSuper:是否调用父类的equals( ) 和 hashCode( )方法 ——true时,先调用父类,再处理自己的字段参数
onlyExplicitlyIncluded:用于控制equals()和hashCode()方法仅包含那些被@EqualsAndHashCode.Include标记的字段(可以通过该方式精确选择哪些字段用于比较) -
@Constructor系列注解-
NoArgsConstructor:用于生成一个无参构造方法。参数
force:如果类中包含final字段,@NoArgsConstructor默认无法生成构造方法,除非添加force=true参数,使final字段自动初始化为默认值。 -
@AllArgsConstructor:用于生成包含所有字段的构造方法。 -
@RequiredArgsConstructor:用于生成包含“必需”字段的构造方法。(即那些final或带有@NonNull注解的字段)
TIP:@NoArgsConstructor和@AllArgsConstructor可以同时使用
-
-
@Data集合注解相当于同时使用如下注解:
@Getter/@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor
参数
staticConstructor:@Data(staticConstuctor = "create") public class User { private Long id; private String username; //... }编译之后,实际生成的代码:
public class User { private Long id; private String username; private User() { } public static User create() { return new User(); } public Long getId() { return this.id; } //... }即@Data(staticConstuctor = "create") 生成了一个名为create()静态实体类生成方法,同时私有化了无参构造器。
注意:1.如果某个字段不想生成某些方法,使用排除参数即可;
2.仅添加@Data注解,默认只会生成一个无参的构造器;
同时添加@Data和@Builder注解时,仅会生成一个全参构造器。
-
@Builder:用于生成建造者模式的代码基本用法:
使用
@Builder注解,Lombok会自动生成一个静态的内部Builder类,包含所有字段的”链式“setter方法,并生成一个build( )方法用于创建对象实例。import lombok.Builder @Builder public class User { private String name; private int age; private String email; }上述代码会自动生成一个User类的Builder,允许使用链式方法来设置字段,并通过build( )方法完成对象的创建,比如像下面这样:
User user = User.builder( ) .name("Alice") .age(25) .email("alice@example.com") .build( );参数
builderMethodName:可以自定义生成的静态Builder方法名称。默认情况下,此方法名为builder( )@Builder(builderMethodName = "newUser") public class User { private String name; private int age; }现在可以使用
User.newUser( )来获取一个Builder实例:User user = User.newUser( ).name("Bob").age(30).build();参数
buildMethodName:用于自定义生成的构建方法的名称,默认为build()@Builder(buildMethodName = "create") public class User { private String name; private int age; }生成的Builder实例可以调用create()方法来构建User对象:
User user = User.builder().name("Charlie").age(40).create();参数
toBuilder:允许在已有对象的基础上创建一个新的builder。它会为生成的类添加toBuilder()方法,返回一个带有当前对象状态的Builder,便于在已有对象的基础上做出小幅修改。import lombok.Builder; @Builder(toBuilder = true) public class User { private String name; private int age; }可以使用toBuilder()方法生成一个新的Builder,并修改部分字段:
User originalUser = User.builder().name("Davaid").age(20).build; User updatedUser = originalUser.toBuilder.age(25).build();注意:@Builder默认不会使用字段的初始值。如果希望Builder生成的对象包含默认值,可以使用
@Builder.Default注解指定。 -
@Value:用于简化不可变对象的创建不可变对象在创建后,其内部状态无法改变,具备线程安全的我,适合并发环境中共享
相当于如下组合:
@Getter@ToString@EqualsAndHashCode@AllArgsConstuctorfinal(所有字段都是自动变为final,只能在构造时赋值)
因此非常适合用来定义数据传输对象(DTO)或不可变的核心数据结构。
基本用法:
在类上添加@Value注解后,Lombok会自动将所有字段设为final并生成只读的getter,禁止字段的修改方法。
默认特性:
- 不可变性:所有字段自动设为final,对象创建后其状态不可更改;
- 私有构造方法:除非显式定义,@Value会默认生成一个私有的无参构造器。一般情况下不会用到,因为@Value的对象要求全部字段初始化。
- 序列化支持:@Value类默认实现 Serializable接口,非常适合在分页式环境中传输数据。
@Value与@Data的区别
@Value @Data getter() √ √ setter() × √ toString() √ √ equals() √ √ hashCode() √ √ 适合不希望被修改的数据对象 适合需要修改的普通Java Bean @Value与可变对象的组合
如果字段是集合类型(如List、Map等),则无法保证集合内容的不可变性。为实现完全不可变性,可以使用
Collections.unmodifiableList()等方法将集合转换为不可变的视图。import lombok.Value; import java.util.List; import java.util.Collections; @Value public class User { String name; List<String> hobbies; public User(String name, List<String> hobbies) { this.name = name; //保证hobbies字段的内容在对象创建后也不可修改 this.hobbies = Collections.unmodifiableList(hobbies); } }在多线程和分布式环境中,通过@Value创建不可变对象,能提升数据安全性和一致性。
-
@Log:用于自动生成日志记录器实例,简化Java类中日志的定义和使用。Lombok支持多个日志框架,每个框架对应一个专门的注解:
@Log:使用java.util.logging@CommonsLog:使用Apache Commons Logginng@Log4j- @
Slf4j @XSlf4j:使用 SLF4J 的扩展(XSLF4J)@JBossLog:使用 JBoss Logging
每个注解都会自动在类中生成一个静态的日志记录器实例,变量名称根据框架的不同,通常是
log每个日志注解生成的
Logger变量都具备以下特性:- 静态和只读:日志记录器实例是
static final类型,确保整个类只会生成一个日志记录器实例 - 命名为
log:生成的变量名称统一为log,便于开发者识别和调用 - 支持日志级别:根据日志框架的不同,
log变量支持相应的日志级别方法(如info、warn、error等)
-
@SneakyThrows:用来“偷偷地”抛出检查型异常(checked exception),而无需显式地在方法签名中声明throws或者使用try-catch。对于需要捕获或声明的异常,@SneakyThrows注解可以让方法绕过这一步。import java.io.UnsupportedEncodingException; public class SneakyThrowsExample { /** * 字节转字符串,且指定了字符编码为 UTF-8 * @param bytes * @return */ public static String utf8ToString(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { System.out.println(utf8ToString("犬小哈教程".getBytes())); } }new String(bytes, "UTF-8")需要显示的进行try-catch异常捕获,或者如下所示,通过throws将异常抛出去,让调用者去处理:
/** * 字节转字符串,且指定了字符编码为 UTF-8 * @param bytes * @return */ public static String utf8ToString(byte[] bytes) throws UnsupportedEncodingException { return new String(bytes, "UTF-8"); }若使用 Lombok 的
@SneakyThrows注解:import lombok.SneakyThrows; public class SneakyThrowsExample { /** * 字节转字符串,且指定了字符编码为 UTF-8 * @param bytes * @return */ @SneakyThrows public static String utf8ToString(byte[] bytes) { return new String(bytes, "UTF-8"); } public static void main(String[] args) { System.out.println(utf8ToString("犬小哈教程".getBytes())); } }在上面的代码中,方法中没有主动
try-catch异常,也没有在方法签名中声明throws,而是在方法上添加了@SneakyThrows注解,此注解会在编译期自动将抛出异常的工作做好,从而让开发者在代码中避免显式的try-catch块或throws声明。但是也容易导致代码不够直观,需要谨慎使用。
Lombok实现原理
Lombok的核心是利用Java注解处理器和AST(抽象语法树)修改技术,在编译阶段动态插入代码。
注解处理器
Java的javax.annotation.processing包提供了注解处理器的支持。Lombok使用该机制,通过实现AnnotationProcessor接口来处理注解。
Lombok编译时注解处理的流程如下:
- 注解扫描:Java编译器在编译过程中扫描所有类上的注解,如果找到Lombok的注解(如@Getter、@Setter等),会将这些注解交给Lombok处理。
- 注解处理:Lombok自定义的注解处理器会在处理阶段解析这些注解,识别哪些注解需要生成代码。
- 修改AST:Lombok的注解处理器会读取编译器生成的AST 树,针对特定注解修改或新增相应的代码节点,例如,针对@Getter注解,Lombok会在类的AST中插入get方法。
- 字节码生成:经过修改的AST树会继续传递给Java编译器,编译器会根据更新后的AST生成新的字节码文件。
AST操作
AST是源代码结构的树状表达形式。Lombok使用Java编译器中不同阶段的AST解析结果来完成代码的插入操作。具体来说 ,Lombok通过对Java编译器的文气,直接操作AST来实现以下功能:
- 方法添加:Lombok通过注解生成getter、setter等方法,将方法节点插入到AST树中;
- 构造器生成:针对@AllArgsConstructor或@NoArgsConstructor等注解,Lombok会自动在AST树中创建构造器节点;
- 字段操作:对于不可变对象的@Value注解,Lombok会将字段设置为final
编译器适配
Lombok需要对不同的Java编译器进行适配,会提供适配层,分别处理Eclipse和IDEA编译环境下手代码生成。
编译期间字节码插桩
Lombok通过编译器插件机制和字节码插桩实现代码的生成。它不需要依赖类加载器,也不会在运行时修改字节码,因此没有额外的性能开销。
Lombok修改的是编译期间的字节码,因此生成的.class文件中已包含所有注解生成的代码,不需要在运行引入Lombok依赖。
优缺点
优点:
- 减少样板代码
- 提高开发效率
- 编译期间优化:Lombok的代码在编译期完成,不会影响运行时性能。
缺点:
- 编译器适配:Lombok依赖编译器的深度集成和AST操作,不同的IDE和编译环境可能需要特殊配置和适配。
- 代码可见性差
- 依赖限制:Lombok是第三方库,尽管已经很成熟,但在某些特定环境下歌熊伟 与其他编译器插件或框架发生冲突。
常见陷阱
-
慎用@Data
当需要自定义一些方法时,比如自定义equals和hashCode方法,但是由于@Data注解自动生成了这些方法,所以你的自定义方法会被覆盖。
可以用callSuper、AccessLevel的属性来定义。
-
注意@ToString的循环引用问题
在处理含有循环引用的数据结构时,要注意。例如,双向链表或者树形结构,其中的元素互相引用。
-
@EqualsAndHashCode与懒加载
当你使用 JPA 或 Hibernate 这样的 ORM 框架时,你可能会使用到懒加载(Lazy Loading)特性。这意味着,某些字段在第一次被访问时才会被加载。而如果你在这样的字段上使用了
@EqualsAndHashCode注解,可能会在你并不希望加载这些字段的情况下触发它们的加载。为了避免这种情况,你可以使用
@EqualsAndHashCode(onlyExplicitlyIncluded = true),然后在你想要包含在equals和hashCode方法中的字段上使用@EqualsAndHashCode.Include。@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class MyEntity { @EqualsAndHashCode.Include private Long id; @OneToMany private List<MyOtherEntity> otherEntities; //... } -
小心@SneakyThrows
@SneakyThrows是一个非常有用的注解,它可以让你 "偷偷" 抛出受检异常,而无需在方法签名中声明它们。但是这也可能会引起一些问题。首先,你的代码的调用者可能并不知道你的方法可能会抛出哪些受检异常,这可能会导致他们在处理异常时疏忽。为了解决这个问题,你应该在你的方法的文档注释中明确说明可能会抛出哪些异常。
其次,
@SneakyThrows会将受检异常包装在RuntimeException中抛出,如果你的代码的调用者捕获了RuntimeException,并且没有检查它的原因,那么他们可能会错过受检异常。为了解决这个问题,你应该尽量避免在你的方法中直接抛出RuntimeException,而是应该抛出具体的异常类型。 -
不要滥用Lombok
(声明:以上学习途径来源于犬小哈教程)
浙公网安备 33010602011771号