一、 前言
Hello~ 大家好。我是秋天的一阵风~
最近被安排到一个新客户项目现场,负责一部分后端服务的部署工作,同时还得处理一些 Java bug。
说到 Lombok 这个工具,我之前只是浅尝辄止,只知道它能帮我自动生成getter和setter方法,还有toString方法,这对我来说就像是个 “懒人包” ,简单方便,但也没去深挖它的其他功能。
这次正好借着这个新项目的机会,我打算好好研究一下Lombok,说不定还能发现一些隐藏的宝藏功能,让它在项目里发挥更大的作用。
二、Lombok是什么?
Lombok 是一个用于 Java 开发的开源库,它通过注解的方式自动生成常见的 Java 代码,如 getter、setter、构造函数、equals、hashCode 等,从而减少样板代码的编写,提高开发效率
Tips: 点击链接前往 Lombok 官方文档地址
三、 Lombok 的工作原理
Lombok 通过注解处理器(Annotation Processor)在编译时生成代码。它利用 Java 编译器的扩展机制,在编译阶段扫描代码中的 Lombok 注解,并根据注解生成相应的代码。此外,Lombok 还使用 ASM 字节码操作库,直接修改生成的字节码,确保生成的代码与手动编写的代码在运行时行为一致。
四、自动生成Getter和Setter
1. 放置位置的灵活性
@Getter和@Setter注解可以放在类级别上,也可以单独放在某个字段上,具体取决于你的需求。
-
放在类上:为类中所有非静态字段自动生成getter或setter方法。
@Getter @Setter public class Account { private String name; private int age; }在这个例子中,
Account类会自动生成getName()、getAge()、setName(String name)和setAge(int age)方法。 -
放在单独的字段上:只为指定的字段生成getter或setter方法。
public class Account { @Getter @Setter private String name; private int age; // 不会为age生成getter或setter方法 }
2. AccessLevel:自定义访问权限
默认情况下,Lombok生成的getter和setter方法都是public的。如果需要自定义访问权限,可以通过AccessLevel属性来指定。例如:
@Getter(AccessLevel.PRIVATE)
public class Account {
private String name;
private int age;
}
AccessLevel的可选值包括:
PUBLIC:生成public方法(默认值)。PACKAGE:生成无访问修饰符的方法(即包级私有)。PROTECTED:生成protected方法。PRIVATE:生成private方法。MODULE:仅限模块内使用(与PACKAGE类似)。NONE:不生成对应的方法,适合排除某些字段。
3. onMethod: 为生成的方法添加注解
通过onMethod属性,可以在生成的getter或setter方法上添加额外的注解。例如,可以给生成的方法添加@Deprecated注解,表示该方法不推荐使用:
@Getter(onMethod_ = {@Deprecated})
private String name;
生成的代码如下:
@Deprecated
@Generated
private String getName() {
return this.name;
}
4. onParam:为方法参数添加注解
通过onParam属性,可以在生成的setter方法的参数上添加注解。例如:
public class Example {
@Getter(onParam = @__({@Override}))
private String name;
@Getter(onParam = @__({@Deprecated}))
private int age;
}
这会在生成的setter方法的参数上添加相应的注解。不过需要注意的是,
onParam属性的使用场景相对较少,且需要谨慎使用。
5. 懒初始化(Lazy)
@Getter和@Setter还支持lazy属性,用于实现懒初始化。懒初始化是一种延迟赋值的策略,通常用于性能优化,避免在对象创建时进行不必要的初始化操作。
@Getter(lazy = true)
private final List<String> expensiveResource = initializeExpensiveResource();
private List<String> initializeExpensiveResource() {
// 模拟耗时的初始化操作
System.out.println("Initializing expensive resource...");
return new ArrayList<>();
}
在这个例子中,expensiveResource字段的值只有在第一次调用getExpensiveResource()方法时才会被初始化,从而避免了在对象创建时就进行耗时的操作。
五、 生成构造函数
Lombok提供了多种注解来简化构造函数的生成,让代码更加简洁易读。
1. @NoArgsConstructor:无参构造函数
@NoArgsConstructor注解用于生成一个无参构造函数。默认情况下,如果类中存在final类型的字段,Lombok不会生成无参构造函数,因为final字段必须在构造函数中初始化。不过,可以通过force属性强制生成无参构造函数,此时Lombok会将所有字段初始化为默认值(例如,int类型初始化为0,String类型初始化为null)。
@NoArgsConstructor(force = true)
public class Account {
private final String name;
private int age;
}
生成的代码如下:
public class Account {
private final String name;
private int age;
public Account() {
this.name = null; // 默认值
this.age = 0; // 默认值
}
}
2. @RequiredArgsConstructor:生成必需参数的构造函数
@RequiredArgsConstructor注解用于生成一个构造函数,该构造函数包含所有final字段和标记为@NonNull的字段。这非常适合在需要确保某些字段在创建对象时必须被初始化的场景中使用。
@RequiredArgsConstructor
public class Account {
private final String name;
private final int age;
private String email; // 非final字段,不会出现在构造函数中
}
生成的代码如下:
public class Account {
private final String name;
private final int age;
private String email;
public Account(String name, int age) {
this.name = name;
this.age = age;
}
}
3. @AllArgsConstructor:全参构造函数
@AllArgsConstructor注解用于生成一个包含所有字段的构造函数。此外,还可以通过staticName属性将构造函数转换为静态工厂方法,让代码更加优雅。
@AllArgsConstructor(staticName = "with")
public class Account {
private String name;
private int age;
private int id;
}
生成的代码如下:
public class Account {
private String name;
private int age;
private int id;
public Account(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
public static Account with(String name, int age, int id) {
return new Account(name, age, id);
}
}
使用静态工厂方法:
Account account = Account.with("小明", 18, 1);
六、@ToString:重写toString方法
@ToString注解用于自动生成toString方法,让对象的字符串表示更加清晰易读。
Lombok提供了多种参数来定制toString方法的行为。
@ToString
@AllArgsConstructor
public class Account {
private String name;
private int age;
private int id;
}
public static void main(String[] args) {
Account account = new Account("小明", 18, 1);
System.out.println(account);
// 输出:Account(name=小明, age=18, id=1)
}
参数说明
-
includeFieldNames:是否在打印内容中带上对应字段名字。默认值为
true,如果设置为false,则只打印字段值。@ToString(includeFieldNames = false) public class Account { private String name; private int age; }输出:
Account(小明, 18) -
exclude:排除不需要打印的字段。可以通过字段名指定需要排除的字段。
@ToString(exclude = "id") public class Account { private String name; private int age; private int id; }输出:
Account(name=小明, age=18) -
of:设置哪些字段需要打印。默认打印所有字段,可以通过字段名指定需要打印的字段。
@ToString(of = {"name", "age"}) public class Account { private String name; private int age; private int id; }输出:
Account(name=小明, age=18) -
callSuper:不仅为当前类中所有字段生成
toString,同时还调用父类的toString方法进行拼接。@ToString(callSuper = true) public class Account extends BaseAccount { private String name; private int age; }假设
BaseAccount类的toString方法返回BaseAccount(...),则输出可能是:Account(BaseAccount(...), name=小明, age=18) -
doNotUseGetters:默认情况下,生成的
toString方法会尽可能使用getter方法获取字段值。如果字段没有getter方法,则直接访问字段。可以通过设置doNotUseGetters = true关闭这个功能。@ToString(doNotUseGetters = true) public class Account { private String name; private int age; } -
onlyExplicitlyIncluded:开启后,将只为字段或
getter方法上添加了@ToString.Include注解的属性生成toString方法,类似于“白名单”模式。@ToString(onlyExplicitlyIncluded = true) public class Account { @ToString.Include private String name; private int age; // 不会出现在toString中 }输出:
Account(name=小明)
七、@EqualsAndHashCode:重写equals和hashCode方法
@EqualsAndHashCode注解用于自动生成equals和hashCode方法。
这两个方法在Java中非常重要,尤其是在对象需要被存储在集合(如HashSet、HashMap)中时。
Lombok提供了多种参数来定制equals和hashCode方法的行为。
@EqualsAndHashCode
public class Account {
private String name;
private int age;
private int id;
}
1. 参数说明
-
of:指定哪些字段需要包含在
equals和hashCode方法中。默认情况下,所有 非静态字段 都会被包含。@EqualsAndHashCode(of = {"name", "age"}) public class Account { private String name; private int age; private int id; // 不会被包含在equals和hashCode中 } -
exclude:排除某些字段,使其不被包含在
equals和hashCode方法中。@EqualsAndHashCode(exclude = "id") public class Account { private String name; private int age; private int id; // 不会被包含在equals和hashCode中 } -
callSuper:是否调用父类的
equals和hashCode方法。默认值为false。@EqualsAndHashCode(callSuper = true) public class Account extends BaseAccount { private String name; private int age; } -
doNotUseGetters:是否使用
getter方法获取字段值。默认值为false,即优先使用getter方法。@EqualsAndHashCode(doNotUseGetters = true) public class Account { private String name; private int age; }
八、@Data:综合注解
@Data注解是一个非常强大的综合注解,它等价于同时使用@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor。使用@Data可以大大简化代码,减少样板代码的编写。
@Data
public class Account {
private String name;
private int age;
private int id;
}
使用@Data后,Account类会自动生成以下内容:
name、age和id的getter和setter方法。equals和hashCode方法。toString方法。- 一个包含所有
final字段和@NonNull字段的构造函数。
九、@Builder:建造者模式
@Builder注解用于实现建造者模式,让对象的创建更加灵活和可读。通过@Builder,可以生成一个内部的建造者类,用于构建对象。
@Builder
public class Account {
private String name;
private int age;
private int id;
}
使用建造者模式创建对象:
Account account = Account.builder().name("小明").age(18).id(1).build();
参数说明
-
builderMethodName:设置生成的
builder方法的名称。默认为builder。@Builder(builderMethodName = "create") public class Account { private String name; private int age; private int id; }使用自定义的
builder方法:Account account = Account.create().name("小明").age(18).id(1).build(); -
buildMethodName:设置生成的
build方法的名称。默认为build。@Builder(buildMethodName = "assemble") public class Account { private String name; private int age; private int id; }使用自定义的
build方法:Account account = Account.builder().name("小明").age(18).id(1).assemble(); -
builderClassName:设置生成的建造者类的名称。默认为
ClassNameBuilder。@Builder(builderClassName = "AccountBuilder") public class Account { private String name; private int age; private int id; } -
toBuilder:生成一个用于将对象转回Builder的方法。这在需要修改对象的某些字段时非常有用。
@Builder(toBuilder = true) public class Account { private String name; private int age; private int id; }使用
toBuilder方法:Account account = Account.builder().name("小明").age(18).id(1).build(); Account updatedAccount = account.toBuilder().name("小红").build(); -
setterPrefixes:设置生成的
setter方法的前缀。默认为set。@Builder(setterPrefixes = "with") public class Account { private String name; private int age; private int id; }使用自定义的
setter方法:Account account = Account.builder().withName("小明").withAge(18).withId(1).build();
十、var和val:类型推断
从Java 10开始,Java引入了var关键字,用于局部变量的类型推断。这使得代码更加简洁,减少了显式声明变量类型的需要。
var name = "小明"; // 类型推断为String
var age = 18; // 类型推断为int
在Java 10之前,可以通过Lombok提供的val关键字实现类似的功能。val表示不可变变量,等价于final。
val name = "小明"; // 类型推断为String,且不可修改
val age = 18; // 类型推断为int,且不可修改
1.使用场景
-
var:用于局部变量的类型推断,变量可以被重新赋值。var list = new ArrayList<String>(); list = new LinkedList<String>(); // 可以重新赋值 -
val:用于局部变量的类型推断,变量不可被重新赋值,类似于final。val list = new ArrayList<String>(); // list = new LinkedList<String>(); // 错误:不能重新赋值
十一、资源释放和异常处理:@Cleanup 和 @SneakyThrows
1. @Cleanup:自动资源释放
在Java中,资源管理是一个重要的问题,尤其是涉及到文件、网络连接等需要手动关闭的资源。Lombok的@Cleanup注解提供了一种更简洁的方式来自动释放资源,类似于Java 7引入的try-with-resources语法,但更加灵活。
示例代码
import lombok.Cleanup;
public static void main(String[] args) throws IOException {
// 使用Lombok的@Cleanup注解自动释放资源
@Cleanup InputStream inputStream = new FileInputStream("test.txt");
byte[] bytes = inputStream.readAllBytes();
System.out.println(new String(bytes));
}
优点
- 简洁性:
@Cleanup注解使得代码更加简洁,避免了冗长的try-finally或try-with-resources块。 - 灵活性:可以应用于任何实现了
AutoCloseable接口的资源,而不仅仅是try-with-resources支持的资源类型。
注意事项
@Cleanup注解的资源必须是局部变量,不能是类的成员变量。- 如果需要处理异常,可以结合
@SneakyThrows注解使用。
2. @SneakyThrows:简化异常处理
Java的异常处理机制要求显式声明或处理异常,这有时会导致代码冗长和复杂。Lombok的@SneakyThrows注解允许开发者在不声明异常的情况下抛出受检查的异常,从而简化代码。
示例代码
import lombok.SneakyThrows;
@SneakyThrows
public static void main(String[] args) {
@Cleanup InputStream inputStream = new FileInputStream("test.txt");
byte[] bytes = inputStream.readAllBytes();
System.out.println(new String(bytes));
}
优点
- 简洁性:避免了显式声明
throws或使用try-catch块。 - 灵活性:可以在不改变方法签名的情况下处理异常。
注意事项
@SneakyThrows注解只能用于方法或构造函数。- 使用时需要谨慎,避免隐藏潜在的异常问题。
十二、非空判断:@NotNull
在Java开发中,非空检查是一个常见的需求,尤其是在处理用户输入或外部数据时。Lombok的@NotNull注解可以自动生成非空检查代码,避免NullPointerException的发生。
示例代码
import lombok.NonNull;
public static void test(@NonNull String name) {
System.out.println(name);
}
生成的代码
public static void test(String name) {
if (name == null) {
throw new NullPointerException("name");
}
System.out.println(name);
}
优点
- 简洁性:减少了显式的非空检查代码。
- 可读性:通过注解清晰地表明参数不能为空。
注意事项
@NonNull注解只能用于方法参数。- 如果需要对类的字段进行非空检查,可以结合
@Builder或@RequiredArgsConstructor注解使用。
十三、锁处理:@Synchronized
在多线程编程中,同步是一个重要的问题。Lombok的@Synchronized注解提供了一种简单的方式来实现方法级别的同步,类似于使用synchronized关键字,但更加灵活。
示例代码
import lombok.Synchronized;
public class Counter {
private int count = 0;
@Synchronized
public void increment() {
count++;
}
@Synchronized("lock")
public void decrement() {
count--;
}
private final Object lock = new Object();
}
优点
- 简洁性:减少了显式的同步代码。
- 灵活性:可以指定同步锁对象,而不仅仅是
this。
注意事项
@Synchronized注解只能用于方法。- 如果需要更复杂的同步逻辑,建议使用
java.util.concurrent包中的工具类。
十四、日志相关:@Log 和 @Slf4j
日志记录是Java开发中的一个重要部分,用于调试、监控和记录运行时信息。Lombok提供了多种日志注解,如@Log、@Slf4j等,可以自动生成日志记录器,简化日志代码的编写。
示例代码
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoggerExample {
public static void main(String[] args) {
log.info("This is an info message");
log.warn("This is a warning message");
log.error("This is an error message");
}
}
生成的代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerExample {
private static final Logger log = LoggerFactory.getLogger(LoggerExample.class);
public static void main(String[] args) {
log.info("This is an info message");
log.warn("This is a warning message");
log.error("This is an error message");
}
}
优点
- 简洁性:减少了显式的日志记录器初始化代码。
- 灵活性:支持多种日志框架,如
SLF4J、Log4j等。
注意事项
- 确保项目中已经引入了相应的日志框架依赖。
- 如果需要更复杂的日志配置,建议在日志框架的配置文件中进行设置。
十五、@Accessors:定制访问器(Getter/Setter)行为
@Accessors注解用于定制生成的getter和setter方法的行为。它提供了多种选项,可以灵活地控制访问器的生成方式,从而满足不同的编程风格和需求。
示例代码
import lombok.Accessors;
@Accessors(chain = true) // 启用链式调用
public class Account {
private String name;
private int age;
}
使用链式调用:
Account account = new Account()
.setName("小明")
.setAge(18);
参数说明
-
chain:是否启用链式调用。如果设置为
true,setter方法将返回this对象,从而支持链式调用。@Accessors(chain = true) public class Account { private String name; private int age; } -
fluent:是否启用“流式”访问器。如果设置为
true,访问器方法将直接以字段名命名,而不是使用get或set前缀。@Accessors(fluent = true) public class Account { private String name; private int age; }使用流式访问器:
Account account = new Account(); account.name("小明").age(18); -
makeFinal:是否将生成的访问器方法标记为
final。如果设置为true,则生成的getter和setter方法将被标记为final,从而防止子类覆盖这些方法。@Accessors(makeFinal = true) public class Account { private String name; private int age; } -
prefix:指定需要生成访问器的字段的前缀。只有字段名以指定前缀开头的字段才会生成访问器。
@Accessors(prefix = "my") public class Account { private String myName; private int age; // 不会生成访问器 }
十六、@FieldDefaults:快速生成字段的权限修饰符
@FieldDefaults注解用于为类中的所有字段指定默认的访问修饰符。这在需要统一设置字段访问权限时非常有用,可以减少重复的修饰符声明,使代码更加简洁。
示例代码
import lombok.experimental.FieldDefaults;
import lombok.AccessLevel;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class Account {
String name; // 默认为private final
int age; // 默认为private final
}
参数说明
-
makeFinal:是否将字段标记为
final。如果设置为true,则所有字段都将被标记为final,从而确保字段的不可变性。@FieldDefaults(makeFinal = true) public class Account { private String name; // private final private int age; // private final } -
level:指定字段的默认访问权限。可选值包括:
PUBLIC:字段为public。PROTECTED:字段为protected。PRIVATE:字段为private(默认值)。PACKAGE:字段为包级私有。MODULE:字段为模块级私有。
@FieldDefaults(level = AccessLevel.PROTECTED) public class Account { String name; // 默认为protected int age; // 默认为protected }