初识 Lombok
Lombok 是什么?它是一个 Java 库,可以通过简单的注解形式来帮助我们简化代码,减少一些必须有但显得很冗余的 Java 代码。最常见的就是比如 getter/setter、日志变量等。通过 Lombok,我们可以以非常干净简洁的风格来写 Java 代码,不再需要编写那些无聊的 boilerplate 代码。
为什么要用它?
Lombok 的主要优点是可以减少 Java 代码的冗余,使代码更简洁。这有以下好处:
- 提高开发效率 - 不需要编写各种 getter/setter、日志语句等重复代码,节约大量时间。
- 代码更干净 - 没有大段冗余代码,逻辑更清晰。
- 更高效优雅 - 通过注解式编程,代码风格更优雅。
- 减少出错 - 自动生成的代码正确性更高。
- 开发人员更愉快 - 不需要编写无聊重复代码,可以更专注于业务
@Getter
@Setter
class User {
private Integer id;
private String username;
private String avatar;
}
class User {
private Integer id;
private String username;
private String avatar;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
// 其他getter/setter方法
}
Lombok 之所以强大,是因为它扩展了 Java 的编译过程。当我们在代码中添加 Lombok 注解时,例如@Getter/@Setter,Lombok 会在编译期间自动扫描这些注解,并为我们生成相应的代码,比如 getter/setter 方法。然后这些自动生成的方法就会被编译进最终的.class 文件中。所以使用 Lombok 注解后得到的 User.class 类文件中,已经包含了完整的 getter/setter 方法,这就是 Lombok 的神奇之处。正因如此,我们在编写代码时只需要添加注解,而不需要手动编写那些冗余代码,这极大地提高了我们的工作效率。
如何使用
使用 Lombok 很简单,只需要几个步骤:
- 在 Maven/Gradle 中添加 Lombok 依赖
- 在 IDE 中安装 Lombok 插件
- 在代码中使用 Lombok 注解
1. 添加 Lombok 依赖
在 Maven 项目中, 可以在 pom 中添加以下依赖来使用 lombok:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
在 Gradle 项目中,可以使用 lombok 插件:
plugins {
id "io.freefair.lombok" version "8.4"
}
除了用 lombok gradle 插件以外,还可以使用 compilyOnly 添加依赖,实现只在编译时引入 lombok 的效果。build.gradle 可以这样配置:
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
testCompileOnly 'org.projectlombok:lombok:1.18.30'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}
在 IDE 中安装 lombok 插件
除了在 maven/gradle 中安装依赖以外,我们还要在 IDE 中安装插件,否则会出现 IDE 报语法错误却能通过编译的现象, 而且 IDE 也无法正确提示自动生成的代码。所以 lombok 是一个侵入性很强的库,只要团队中有一个人用 lombok 开发, 那么所有人都必须安装 插件。不过好在 lombok 官方提供了丰富的插件支持,目前市面绝大多数流行的 IDE 和构建工具都有提供支持。具体可以参考官方文档,这里只摘抄了 IDEA 和 VsCode 作为例子。
1. 在 IDEA 中安装 lombok 插件
IntelliJ IDEA 2020.3 到 2023.1 之间的版本开始原生兼容 Lombok,无需安装插件即可使用。但在 2020.3 之前或 2023.1 之后的版本中,仍需借助 Lombok 插件来支持 Lombok 的所有功能。
对于这些版本的 IntelliJ IDEA,可以通过以下步骤安装 Lombok 插件:
- 点击顶部菜单
“File > Settings > Plugins”
- 在弹出的窗口中,点击
“Browse repositories...”
- 在搜索框内输入
“Lombok Plugin”
进行搜索 - 在搜索结果中找到
“Lombok Plugin”
,点击“Install”
进行安装 - 安装完成后重启 IntelliJ IDEA
2. 在 VsCode 中安装 lombok 插件
Extension Pack for Java 插件内置支持 lombok。
- 按
ctrl+shift+X
打开扩展管理器 - 输入
java
查找插件,并点击 “安装” - 最后重新载入 VsCode
Lombok 用法
val
用在局部变量前面,将变量声明为 final
import java.util.ArrayList;
import java.util.HashMap;
import lombok.val;
public class ValExample {
public String example() {
val example = new ArrayList<String>();
example.add("Hello, World!");
val foo = example.get(0);
return foo.toLowerCase();
}
public void example2() {
val map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for (val entry : map.entrySet()) {
System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
}
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class ValExample {
public String example() {
final ArrayList<String> example = new ArrayList<String>();
example.add("Hello, World!");
final String foo = example.get(0);
return foo.toLowerCase();
}
public void example2() {
final HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for (final Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
}
}
}
@Getter/@Setter
在属性上使用,让 lombok 自动生成对应的 getter/setter ,它还可以指定访问范围(包括:PUBLIC, PROTECTED, PACKAGE 和 PRIVATE)。如果将访问级别设置为特殊的 AccessLevel.NONE
,就可以用来禁用 getter/setter 生成, 在类上使用 @Data 注释时,可以用此方法来禁用某些字段生成 getter/setter 方法。
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
}
public class GetterSetterExample {
private int age = 10;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
protected void setName(String name) {
this.name = name;
}
}
@ToString
在类上添加 @ToString
注解,可以自动覆写 toString 方法,还可以排除指定的属性以及设置在输出中包含父类的 toString 方法输出。
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;
}
}
}
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 和 hashCode 方法, 默认情况下会使用所有非静态、非顺时字段,也可以通过 @EqualsAndHashCode.Include 和 @EqualsAndHashCode.Exclude 来标记要包含和排除哪些字段。如果希望在一个子类的方法中包含父类的 equals/hashCode 调用,则需要将 callSuper 设置为 true。
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;
}
}
}
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 用于生成无参构造函数
- @RequiredArgsConstructor 注解可以用来自动生成指定参数的构造方法。它所生成的构造方法,参数来源有两个:
- 类中所有未初始化的final字段
- 被@NonNull注解标记过的字段
对于这两类字段,@RequiredArgsConstructor会自动为它们生成构造方法参数。并且为@NonNull标记的字段生成显式空检查,在接收到 null 参数值时抛出 NPE 异常。
- @AlllArgsConstructor 注解可以用来自动生成全参构造方法,标记为@NonNull的字段的参数还会自动加上空值校验。
另外,这些注解生成的构造方法参数顺序与类中字段声明的顺序一致。如果指定staticName="of"
参数,则这些注解会生成一个返回类对象的静态工厂方法,比直接使用 new 语句方便很多。
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;
}
}
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
在类上添加 @Data
注解,相当于同时添加了 @ToString
, @EqualsAndHashCode
、@Getter
、@Setter
和@RequiredArgsConstrutor
这些注解,所有字段的 getter 方法 , 非 final 字段的 setter 方法,以及 toString, equals 和 hashCode 方法都会自动生成,对于快速实现 POJO 十分有用。
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;
}
}
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;
}
}
}
@Log
lombok 为不同的日志框架内置支持了几种不同的注解,这些注解都是在类上生成一个名为 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);
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");
}
}
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 还提供了自定义日志框架功能,只需要在类上添加@CustomLog
注解,并且在 lombok.config 配置文件中指定 lombok.log.custom.declaration
配置项即可:
# lombok.config
lombok.log.custom.declaration = com.foo.your.Logger com.foo.your.LoggerFactory.createYourLog(TYPE)(TOPIC)
最后生成的 log 静态常量就像这样:
private static final com.foo.your.Logger log = com.foo.your.LoggerFactory.createYourLogger(LogExample.class);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗