Java Lombok @Data 注解用法详解
Lombok 的 @Data
注解是一个极其强大且常用的注解,它旨在极大简化 Java Bean 或值对象(如 POJO)的代码编写。它本质上是多个 Lombok 注解的组合,自动为你生成大量样板代码。
核心功能:
@Data
等价于同时使用了以下注解:
-
@Getter
: 为所有非静态字段生成public
的 getter 方法。 -
@Setter
: 为所有非 final 且非静态的字段生成public
的 setter 方法。 -
@ToString
: 生成toString()
方法,默认包含所有非静态字段。 -
@EqualsAndHashCode
: 生成equals(Object other)
和hashCode()
方法,默认使用所有非静态、非瞬态字段(除非用@EqualsAndHashCode.Exclude
排除)。 -
@RequiredArgsConstructor
: 生成一个包含所有final
字段以及标记为@NonNull
且未在声明时初始化的字段的构造函数。
用法:
直接在类声明上方添加 @Data
注解即可。
java
复制
下载
import lombok.Data;@Data public class User {private final Long id; // 包含在 RequiredArgsConstructor 中private String username;private String email;private int age;private boolean active;// ... 可能还有其他字段 }
Lombok 自动生成的代码等价于:
java
复制
下载
public class User {private final Long id;private String username;private String email;private int age;private boolean active;// @RequiredArgsConstructor 生成的public User(Long id) {this.id = id;}// @Getter 生成的public Long getId() {return this.id;}public String getUsername() {return this.username;}public String getEmail() {return this.email;}public int getAge() {return this.age;}public boolean isActive() { // boolean 类型特殊,getter 通常是 isXxx()return this.active;}// @Setter 生成的 (注意:id 是 final,所以没有 setId)public void setUsername(String username) {this.username = username;}public void setEmail(String email) {this.email = email;}public void setAge(int age) {this.age = age;}public void setActive(boolean active) {this.active = active;}// @EqualsAndHashCode 生成的@Overridepublic boolean equals(Object o) {if (o == this) return true;if (!(o instanceof User)) return false;User other = (User) o;if (!other.canEqual((Object) this)) return false;if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;if (this.getUsername() == null ? other.getUsername() != null : !this.getUsername().equals(other.getUsername())) return false;if (this.getEmail() == null ? other.getEmail() != null : !this.getEmail().equals(other.getEmail())) return false;if (this.getAge() != other.getAge()) return false;if (this.isActive() != other.isActive()) return false;return true;}@Overridepublic int hashCode() {int PRIME = 59;int result = 1;result = result * PRIME + (this.getId() == null ? 43 : this.getId().hashCode());result = result * PRIME + (this.getUsername() == null ? 43 : this.getUsername().hashCode());result = result * PRIME + (this.getEmail() == null ? 43 : this.getEmail().hashCode());result = result * PRIME + this.getAge();result = result * PRIME + (this.isActive() ? 79 : 97);return result;}// @ToString 生成的@Overridepublic String toString() {return "User(id=" + this.getId() + ", username=" + this.getUsername() + ", email=" + this.getEmail() + ", age=" + this.getAge() + ", active=" + this.isActive() + ")";} }
关键点解释和注意事项:
-
构造函数 (
@RequiredArgsConstructor
):-
只生成包含所有
final
字段和标记为@NonNull
且未在声明时初始化的字段的构造函数。 -
如果你需要一个无参构造函数或者包含所有字段的构造函数,需要显式添加
@NoArgsConstructor
或@AllArgsConstructor
。 -
示例: 在上面的
User
类中,只有id
是final
,所以只生成User(Long id)
构造函数。如果需要无参构造,要加上@NoArgsConstructor
。
-
-
Getter/Setter:
-
Getter 对所有字段生成。
-
Setter 只对非 final 且非静态字段生成。
-
布尔字段 (
boolean
) 的 getter 默认命名为isFieldName()
。 -
如果需要改变访问级别(如
protected
或包私有)、懒加载 (@Getter(lazy=true)
) 或自定义逻辑,应使用单独的@Getter
/@Setter
注解而不是@Data
。
-
-
toString()
(@ToString
):-
默认包含所有非静态字段。
-
可以使用
@ToString.Exclude
排除特定字段:@ToString.Exclude private String password;
-
可以使用
@ToString(onlyExplicitlyIncluded = true)
然后配合@ToString.Include
来只包含标记的字段。 -
可以使用
@ToString(callSuper = true)
来包含父类的toString()
结果(默认是false
)。
-
-
equals()
和hashCode()
(@EqualsAndHashCode
):-
极其重要! 默认使用所有非静态、非瞬态 (
transient
) 字段来计算。 -
使用
@EqualsAndHashCode.Exclude
排除特定字段。 -
使用
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
配合@EqualsAndHashCode.Include
来只包含标记的字段(推荐做法,避免意外包含不相关字段导致逻辑错误)。 -
使用
@EqualsAndHashCode(callSuper = true)
在计算时考虑父类的equals
/hashCode
(默认是false
)。如果类有继承父类(非Object
),强烈建议仔细考虑是否需要设置callSuper = true
,这通常是需要的,以避免违反等价关系契约。
-
-
final
和@NonNull
字段:-
final
字段会被包含在@RequiredArgsConstructor
中,且不会生成 setter。 -
如果
@NonNull
字段在声明时未初始化,它会被包含在@RequiredArgsConstructor
中,并且生成的 setter 和构造器会自动添加 null 检查(抛出NullPointerException
)。如果字段在声明时已初始化,则不会被包含在构造器中。
-
最佳实践和常见场景:
-
简单数据传输对象 (DTO) / 值对象 (VO):
@Data
是理想选择,能极大简化代码。 -
JPA/Hibernate 实体 (Entity):
-
可以使用
@Data
。 -
但要非常小心
equals()
/hashCode()
:-
避免使用自动生成的代理键 (如
@Id @GeneratedValue
) 或者可变字段(如集合关联)来计算equals
/hashCode
,尤其是在实体还未持久化(id 为 null)或关联被修改时。推荐使用业务键(如唯一用户名、组合字段)或依赖数据库唯一标识但要确保一致性(有时用id
但只在持久化后有效)。 -
通常需要显式配置
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
并用@EqualsAndHashCode.Include
标记选定的稳定业务键字段。
-
-
注意生成的 setter 是 public 的,有时你可能希望控制对某些字段(如关联集合)的修改,这时可能需要使用单独的
@Setter(AccessLevel.PROTECTED)
或避免 setter 而提供业务方法。 -
小心循环引用在
toString()
中导致StackOverflowError
(尤其在双向关联时),用@ToString.Exclude
排除关联字段。
-
-
不可变对象: 如果目标是创建不可变对象,将所有字段设为
final
,然后使用@Data
(或@Value
)。@Data
会生成包含所有final
字段的构造器 (@RequiredArgsConstructor
),且不会为final
字段生成 setter。 -
需要定制化时: 如果
@Data
的默认行为不完全符合要求(如toString
格式、equals
逻辑、部分字段不需要 setter/getter、需要其他构造器等),不要犹豫,拆开使用单独的 Lombok 注解(@Getter
,@Setter
,@ToString
,@EqualsAndHashCode
,@NoArgsConstructor
,@AllArgsConstructor
,@RequiredArgsConstructor
,@Builder
等)。@Data
是方便的快捷方式,但不是万能药。
配置依赖 (Maven):
确保在项目的 pom.xml
中添加 Lombok 依赖(作用域为 provided
,因为它是编译时注解处理器)和注解处理器路径:
xml
复制
下载
运行
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version> <!-- 使用最新稳定版本 --><scope>provided</scope></dependency> </dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version> <!-- 使用兼容版本 --><configuration><source>17</source> <!-- 根据你的JDK版本调整 --><target>17</target><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></path></annotationProcessorPaths></configuration></plugin></plugins> </build>
IDE 支持:
为了让 IDE(如 IntelliJ IDEA, Eclipse)能识别 Lombok 生成的代码并避免编译错误,必须安装对应的 Lombok 插件。具体安装方法请参考 Lombok 官网或 IDE 的插件市场。
总结:
Lombok 的 @Data
注解通过自动生成 getter、setter、toString
、equals
、hashCode
和特定构造器,显著减少了 Java Bean 中的样板代码,提高了开发效率和代码简洁性。然而,理解其默认行为(特别是关于 equals
/hashCode
、构造器生成规则和字段排除)以及何时需要定制化(使用单独的 Lombok 注解)至关重要,尤其是在处理 JPA 实体、继承或需要特定行为时。正确使用 @Data
能让你的代码更干净、更易维护。