码森林 - AI 赋以翼,赋 AI 以魂

Lombok 使用详解

2025/12/03
6
0

简介

Lombok 是一个 Java 库,通过注解的方式自动生成 getter/setter、equals、hashCode、toString、构造器等通用代码,显著减少样板代码的编写。

为什么使用 Lombok?

// 不使用 Lombok(70+ 行)
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;
    
    public User() {}
    public User(Long id, String name, String email, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }
    
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // ... 更多 getter/setter
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) &&
               Objects.equals(name, user.name) &&
               Objects.equals(email, user.email) &&
               Objects.equals(age, user.age);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, name, email, age);
    }
    
    @Override
    public String toString() {
        return "User{" + "id=" + id + ", name='" + name + '\'' + 
               ", email='" + email + '\'' + ", age=" + age + '}';
    }
}
​
// 使用 Lombok(5 行)
@Data
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;
}

核心注解详解

1. @Getter 和 @Setter

生成 getter 和 setter 方法。

@Getter
@Setter
public class User {
    private String name;
    private Integer age;
}
​
// 等价于
public class User {
    private String name;
    private Integer age;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

参数:

  • AccessLevel:控制访问级别(PUBLIC、PROTECTED、PACKAGE、PRIVATE)

@Getter(AccessLevel.PROTECTED)
@Setter(AccessLevel.PRIVATE)
public class User {
    private String name;
}
​
// name 的 getter 为 protected,setter 为 private

字段级别使用:

public class User {
    @Getter
    private String name;
    
    @Getter @Setter
    private Integer age;
}

2. @Data

综合注解,包含 @Getter、@Setter、@ToString、@EqualsAndHashCode、@RequiredArgsConstructor。

@Data
public class User {
    private Long id;
    private String name;
    private String email;
}
​
// 自动生成:
// - 所有字段的 getter/setter
// - toString()
// - equals() 和 hashCode()
// - 包含 final 字段的构造器

最常用的通用注解,适合 DTO、DO、VO。


3. @Builder

构建者模式,支持链式构建对象。

@Builder
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;
}
​
// 使用
User user = User.builder()
    .id(1L)
    .name("张三")
    .email("zhangsan@example.com")
    .age(25)
    .build();

优势:

  • 参数多时代码可读性强

  • 类型安全

  • 支持默认值

@Builder(builderMethodName = "customBuilder")
public class User {
    @Builder.Default
    private Long id = 0L;
    
    private String name;
}
​
// 自定义构建器方法名
User user = User.customBuilder()
    .name("张三")
    .build(); // id 默认为 0

与 @Data 结合:

@Data
@Builder
public class Company {
    private Long companyId;
    private String companyName;
    private String businessLicenseCode;
}
​
// 既可以用构建器,也有 getter/setter

4. @NoArgsConstructor 和 @AllArgsConstructor

生成无参和全参构造器。

@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}
​
// 等价于
public class User {
    public User() {}
    
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

@RequiredArgsConstructor: 只包含 final 和 @NonNull 字段的构造器

@RequiredArgsConstructor
public class User {
    private final String name;  // 会被包含在构造器中
    private Integer age;         // 不会被包含
    @NonNull
    private String email;        // 会被包含在构造器中
}
​
// 等价于
public class User {
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

5. @ToString

生成 toString() 方法。

@ToString
public class User {
    private String name;
    private String email;
    private Integer age;
}
​
// 输出:User(name=张三, email=zhangsan@example.com, age=25)

参数:

  • exclude:排除某些字段

@ToString(exclude = {"password", "email"})
public class User {
    private String name;
    private String password;
    private String email;
}
​
// 输出:User(name=张三)
  • includeFieldNames:是否包含字段名

@ToString(includeFieldNames = false)
public class User {
    private String name;
    private Integer age;
}
​
// 输出:User(张三, 25)

6. @EqualsAndHashCode

生成 equals() 和 hashCode() 方法。

@EqualsAndHashCode
public class User {
    private Long id;
    private String name;
    private String email;
}
​
// 自动生成 equals() 和 hashCode(),用于集合比较

参数:

  • exclude:排除某些字段(不参与比较)

@EqualsAndHashCode(exclude = {"createTime", "updateTime"})
public class User {
    private Long id;
    private String name;
    private String createTime;
    private String updateTime;
}
​
// 比较时只看 id 和 name
  • callSuper:是否调用父类的 equals() 和 hashCode()

@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    private String name;
}
​
// 比较时会调用父类的方法

7. @NonNull

在 setter 或构造器中进行非空检查。

@Setter
public class User {
    @NonNull
    private String name;
}
​
// 生成的 setter 会包含非空检查
public void setName(@NonNull String name) {
    if (name == null) {
        throw new NullPointerException("name is marked @NonNull but is null");
    }
    this.name = name;
}

8. @Accessors

自定义 getter/setter 的生成方式。

@Getter
@Setter
@Accessors(chain = true)
public class User {
    private String name;
    private Integer age;
}
​
// setter 返回 this,支持链式调用
User user = new User()
    .setName("张三")
    .setAge(25);

参数:

  • fluent = true:去掉 get/set 前缀

@Getter
@Setter
@Accessors(fluent = true)
public class User {
    private String name;
}
​
// 生成 name() 和 name(String) 而不是 getName() 和 setName()
String name = user.name();
user.name("张三");
  • prefix:指定字段前缀

@Getter
@Setter
@Accessors(prefix = "m_")
public class User {
    private String m_name;
}
​
// 生成 getName() 而不是 getM_name()

9. @Slf4j(日志注解)

自动注入 SLF4J 日志对象。

@Slf4j
public class UserService {
    public void createUser(User user) {
        log.info("创建用户: {}", user);
        log.debug("详细信息: {}", user.toString());
        log.error("发生错误", new Exception());
    }
}
​
// 等价于
public class UserService {
    private static final Logger log = LoggerFactory.getLogger(UserService.class);
    
    public void createUser(User user) {
        log.info("创建用户: {}", user);
    }
}

其他日志注解:

  • @Log:java.util.logging

  • @Log4j:Apache Log4j

  • @Log4j2:Apache Log4j 2

  • @CommonsLog:Apache Commons Logging

  • @SLF4J:SLF4J with Logback(推荐)


10. @Value

不可变对象(相当于 @Data + @Setter(PRIVATE) + final)。

@Value
public class User {
    private String name;
    private Integer age;
}
​
// 等价于
public class User {
    private final String name;
    private final Integer age;
    
    // 只有 getter,没有 setter
    // equals, hashCode, toString 自动生成
}

11. @Synchronized

为方法添加同步锁。

@Synchronized
public void updateUser(User user) {
    // 方法体
}
​
// 等价于
private final Object $lock = new Object[0];
​
public void updateUser(User user) {
    synchronized ($lock) {
        // 方法体
    }
}

12. @SneakyThrows

将检查异常转换为未检查异常。

@SneakyThrows
public void readFile(String path) {
    new FileInputStream(path).read();
}
​
// 等价于
public void readFile(String path) throws IOException {
    new FileInputStream(path).read();
}

谨慎使用! 可能隐藏异常。


13. @Cleanup

自动关闭资源(类似 try-with-resources)。

@Cleanup
InputStream in = new FileInputStream("file.txt");
​
// 等价于
InputStream in = new FileInputStream("file.txt");
try {
    // 使用 in
} finally {
    in.close();
}

14. @With(复制构造器)

创建对象的副本并修改某个字段。

@With
@Value
public class Point {
    private int x;
    private int y;
}
​
Point p1 = new Point(1, 2);
Point p2 = p1.withX(3);  // 创建 (3, 2) 的新对象

实战示例

DDD 领域模型示例

聚合根

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Company {
    private Long companyId;
    private String companyName;
    private String businessLicenseCode;
    private CompanyStatus status;
    private Boolean deleted;
    
    @ToString.Exclude  // 避免 toString 时过多信息
    private List<BusinessUser> userList;
    
    @ToString.Exclude
    private List<BusinessRole> roleList;
    
    // 业务方法
    public void enable() {
        this.status = CompanyStatus.ENABLED;
    }
    
    public boolean isEnabled() {
        return status == CompanyStatus.ENABLED && !deleted;
    }
}

值对象

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode  // 值对象必须有 equals/hashCode
public class CompanyRegisterInfo {
    private String companyName;
    private String businessLicenseCode;
    private String legalRepresentative;
    private String legalIdNumber;
    
    public boolean isComplete() {
        return companyName != null && !companyName.isEmpty() &&
               businessLicenseCode != null && !businessLicenseCode.isEmpty();
    }
}

实体

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BusinessUser {
    private Long userId;
    private Long companyId;
    private String userName;
    private String loginAccount;
    
    @ToString.Exclude  // 密码不打印
    private String password;
    
    private Boolean isRoot;
    private Boolean enabled;
    
    public void enable() {
        this.enabled = true;
    }
    
    public void disable() {
        this.enabled = false;
    }
}

领域服务

@Slf4j
public class MerchantDomainService {
    private final CompanyRepository companyRepository;
    private final BusinessUserRepository businessUserRepository;
    
    @Synchronized
    public Company enableCompany(Company company) {
        if (!company.getRegisterInfo().isComplete()) {
            throw new IllegalStateException("信息不完整");
        }
        
        log.info("启用商户: {}", company.getCompanyId());
        company.enable();
        return companyRepository.save(company);
    }
}

最佳实践

1. 选择合适的注解组合

// DTO/DO(数据传输对象)
@Data
@Builder
public class UserDTO {
    private Long id;
    private String name;
}
​
// 值对象(不可变)
@Value
@Builder
public class AddressVO {
    private String province;
    private String city;
    private String district;
}
​
// 聚合根(可变)
@Data
@Builder
public class UserAggregate {
    private Long userId;
    private String name;
    private AddressVO address;
}

2. 合理使用 @ToString.Exclude

@Data
public class User {
    private Long id;
    private String name;
    
    @ToString.Exclude  // 避免序列化大对象
    private List<Order> orders;
    
    @ToString.Exclude  // 避免打印敏感信息
    private String password;
}

3. 日志字段不需要 getter

@Slf4j
@Data
public class UserService {
    public void process() {
        log.info("处理数据");  // log 直接可用
    }
}

4. 构造器字段访问控制

@Data
@RequiredArgsConstructor
public class Repository {
    @NonNull
    private final DatabaseConnection connection;  // 必须通过构造器传入
    
    private String name;  // 可以通过 setter 修改
}

5. 避免循环引用时的无限递归

@Data
public class User {
    private Long id;
    private String name;
    
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private Company company;  // 避免在 toString/equals 时循环引用
}
​
@Data
public class Company {
    private Long id;
    
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private List<User> users;
}

6. 继承时谨慎使用 callSuper

@Data
public class BaseEntity {
    private Long id;
    private String createTime;
}
​
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    private String name;
}
​
// equals() 会包含父类的 id 和 createTime

常见问题

Q1: 为什么 @Data 不能用在接口上?

// ❌ 错误
@Data
public interface UserService {
}
​
// ✅ 正确
@Data
public class User {
}

Lombok 只能注解在类上,接口默认所有字段都是 public static final。


Q2: 如何处理 JPA 的 @OneToMany 循环引用?

@Entity
@Data
public class Company {
    @Id
    private Long id;
    
    @OneToMany
    @ToString.Exclude  // 避免循环打印
    @EqualsAndHashCode.Exclude  // 避免循环比较
    private List<User> users;
}
​
@Entity
@Data
public class User {
    @Id
    private Long id;
    
    @ManyToOne
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private Company company;
}

Q3: @Builder 和 @Data 一起用时的问题?

@Data
@Builder
public class User {
    private String name;
    private Integer age;
}
​
// 既能用 builder
User u1 = User.builder().name("张三").age(25).build();
​
// 也能用 setter
User u2 = new User();
u2.setName("李四");
u2.setAge(30);
​
// 还能用 toString/equals
System.out.println(u1);
boolean same = u1.equals(u2);

完全兼容,no问题。


Q4: Lombok 在 IDE 中无法识别生成的方法?

解决方案:

  1. 安装 Lombok 插件(IntelliJ IDEA 已内置)

  2. 启用 IDE 的 Annotation Processing

    • IntelliJ: Settings → Compiler → Annotation Processors → Enable annotation processing

  3. 重新启动 IDE


Q5: 能否自定义生成的方法?

@Getter
public class User {
    private String name;
    
    // 自定义 getter
    public String getName() {
        return name == null ? "Unknown" : name.toUpperCase();
    }
}
​
// Lombok 不会再生成 getter,使用你自定义的版本

Q6: @Builder 的默认值问题

@Builder
public class Config {
    @Builder.Default
    private int timeout = 30;
    
    @Builder.Default
    private String host = "localhost";
    
    private int port;  // 必须指定
}
​
// 使用
Config cfg = Config.builder()
    .port(8080)
    .build();
// timeout = 30, host = "localhost", port = 8080

性能考虑

1. 生成代码的大小

注解

代码行数增加

说明

@Getter/@Setter

只是简单 getter/setter

@Data

包含多个方法

@Builder

中-高

生成完整 builder 类

@EqualsAndHashCode

复杂的比较逻辑

2. 运行时性能

Lombok 注解只在编译时生成代码,运行时零开销

// 编译后的字节码
public class User {
    private String name;
    
    public String getName() {  // 编译时生成
        return this.name;
    }
}

3. 内存占用

  • 生成的 equals() 方法会比手写版本多占用内存

  • 对于大规模对象集合,可考虑 exclude 减少字段比较

@EqualsAndHashCode(exclude = "largeList")
public class Document {
    private String content;
    private List<byte[]> largeList;  // 不参与比较
}

总结

注解

场景

优先级

@Data

通用 DO/DTO

⭐⭐⭐⭐⭐

@Builder

参数多的对象

⭐⭐⭐⭐⭐

@Value

值对象、不可变对象

⭐⭐⭐⭐

@Slf4j

日志记录

⭐⭐⭐⭐⭐

@NoArgsConstructor/@AllArgsConstructor

特定需求

⭐⭐⭐

@NonNull

参数校验

⭐⭐⭐

@Accessors

链式调用

⭐⭐⭐

其他

特殊场景

⭐⭐

推荐组合方案

// 简单数据对象
@Data
public class SimpleDTO { }
​
// 复杂对象需要灵活构建
@Data
@Builder
public class ComplexEntity { }
​
// 不可变值对象
@Value
public class ValueObject { }
​
// 有日志需求的服务
@Slf4j
@Service
public class MyService { }

相关资源

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>