五分钟学会 Java 开发效率神器 —— Lombok

BOSS攻略 2026-09-01 17:33:49

初识 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:

org.projectlombok

lombok

1.18.30

provided

在 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();

example.add("Hello, World!");

val foo = example.get(0);

return foo.toLowerCase();

}

public void example2() {

val map = new HashMap();

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 example = new ArrayList();

example.add("Hello, World!");

final String foo = example.get(0);

return foo.toLowerCase();

}

public void example2() {

final HashMap map = new HashMap();

map.put(0, "zero");

map.put(5, "five");

for (final Map.Entry 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 {

private int x, y;

@NonNull private T description;

@NoArgsConstructor

public static class NoArgsExample {

@NonNull private String field;

}

}

public class ConstructorExample {

private int x, y;

@NonNull private T description;

private ConstructorExample(T description) {

if (description == null) throw new NullPointerException("description");

this.description = description;

}

public static ConstructorExample of(T description) {

return new ConstructorExample(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 {

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 {

private final String name;

private final T value;

private Exercise(String name, T value) {

this.name = name;

this.value = value;

}

public static Exercise of(String name, T value) {

return new Exercise(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);