Lombok 构造相关核心注解全解析

一、核心注解基础细节(4 个构造相关注解,逐个拆透)

先明确 4 个核心注解的 "底层定义 + 生成规则 + 细节补充",每个注解都包含 "基础作用 + 生成逻辑 + 隐藏细节 + 代码示例 + 等价手动代码",不跳过任何一个细节。

1. @RequiredArgsConstructor(重点中的重点,Spring 开发必用)

1.1 底层定义(精准无歧义)

Lombok 提供的 "必需参数构造方法生成注解",核心作用是:仅为类中 "必须赋值的字段" 生成构造方法,且自动为 @NonNull 字段添加非空校验,无需手动编写任何代码。

1.2 关键细节 1:什么是 "必须赋值的字段"(仅 2 种,无第三种)

注解只会识别以下两类字段作为构造方法的参数,普通字段(既非 final 也非 @NonNull)绝对不会出现在构造参数中,这是核心细节,必须记死:

  • 字段 1:被 final 修饰的字段
    • 细节 1:final 字段的特性是 "必须在对象创建时完成赋值"(要么直接给默认值,要么通过构造方法赋值),因此 @RequiredArgsConstructor 会将其视为 "必需字段";
    • 细节 2:如果 final 字段已经直接赋值(如private final int age = 18;),则不会出现在构造方法参数中(因为已经完成赋值,无需通过构造方法传入);
    • 细节 3:如果 final 字段没有默认值,且未被 @RequiredArgsConstructor 纳入构造参数(比如注解未添加),则编译报错(违背 final 字段的赋值规则)。
  • 字段 2:被 @NonNull 修饰的字段
    • 细节 1:@NonNull 是 Lombok 的非空标记注解,作用是 "强制该字段不能为 null",因此 @RequiredArgsConstructor 会将其视为 "必需字段",必须通过构造方法传入非 null 值;
    • 细节 2:@NonNull 仅作用于 "构造方法参数" 和 "setter 方法",不会影响字段本身的赋值逻辑,仅做 "非空校验";
    • 细节 3:如果 @NonNull 字段同时被 final 修饰,依然会被纳入构造参数,但如果 final 有默认值,则不会纳入(优先级:final 默认值 > @NonNull)。
1.3 关键细节 2:自动生成的构造方法的特性
  • 特性 1:构造方法的访问修饰符是 "public"(默认),如果需要 private/protected,可通过注解参数设置:@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
  • 特性 2:构造方法的参数顺序 = 类中 "必需字段" 的声明顺序(先声明的字段,先作为构造参数),与字段类型、注解顺序无关;
  • 特性 3:自动为 @NonNull 字段添加非空校验逻辑,校验失败会抛出 NullPointerException,异常信息固定为 "字段名 is marked non-null but is null",无法自定义(除非手动写构造方法);
  • 特性 4:Lombok 不会覆盖手动编写的构造方法,如果手动写了与 @RequiredArgsConstructor 生成的构造方法参数一致的构造方法,注解生成的构造方法会失效(优先级:手动构造 > 注解生成)。
1.4 代码示例(含所有细节场景)

场景 1:包含 final(无默认值)、@NonNull、普通字段

java 复制代码
import lombok.RequiredArgsConstructor;
import lombok.NonNull;

// 注解生成必需参数构造方法,访问修饰符默认public
@RequiredArgsConstructor
public class UserService {
    // 必需字段1:final修饰(无默认值)→ 纳入构造参数
    private final UserMapper userMapper;
    
    // 必需字段2:@NonNull修饰(非final)→ 纳入构造参数
    @NonNull
    private String serviceName;
    
    // 必需字段3:final修饰(有默认值)→ 不纳入构造参数
    private final int maxCount = 100;
    
    // 普通字段:既非final也非@NonNull → 不纳入构造参数
    private int timeout = 30;
}
1.5 等价手动代码(看清 Lombok 到底生成了什么)
java 复制代码
public class UserService {
    private final UserMapper userMapper;
    @NonNull
    private String serviceName;
    private final int maxCount = 100;
    private int timeout = 30;

    // @RequiredArgsConstructor自动生成的构造方法
    public UserService(UserMapper userMapper, String serviceName) {
        // 自动为@NonNull字段添加非空校验
        if (serviceName == null) {
            throw new NullPointerException("serviceName is marked non-null but is null");
        }
        // 为final字段赋值(无默认值)
        this.userMapper = userMapper;
        // 为@NonNull字段赋值
        this.serviceName = serviceName;
        // maxCount有默认值,无需赋值;timeout是普通字段,无需赋值
    }
}
1.6 实战核心场景(Spring 构造器注入,细节拉满)

这是 @RequiredArgsConstructor 最常用的场景,Spring 官方推荐 "构造器注入"(比 @Autowired 字段注入更安全、可测试性更好),注解能彻底简化代码,细节如下:

  • 细节 1:Spring 构造器注入的核心要求:依赖字段用 final 修饰,且有对应的构造方法(参数包含所有 final 依赖);
  • 细节 2:@RequiredArgsConstructor 会自动生成 "包含所有 final 字段" 的构造方法,因此无需手动写构造方法,也无需加 @Autowired(Spring 4.3+ 会自动识别构造器注入,无需 @Autowired);
  • 细节 3:如果依赖字段没有用 final 修饰,即使加了 @RequiredArgsConstructor,也不会被纳入构造参数,无法完成注入;
  • 细节 4:如果有多个 final 依赖,构造方法的参数顺序 = 依赖字段的声明顺序,Spring 会按参数类型 + 顺序自动匹配注入,无需担心顺序问题。

代码示例(实战标准写法):

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service // Spring服务类
@RequiredArgsConstructor // 自动生成包含所有final依赖的构造方法
public class OrderService {
    // 细节1:依赖用final修饰 → 纳入构造参数,完成注入
    private final OrderMapper orderMapper;
    private final UserService userService;
    
    // 细节2:无需手动写构造方法,无需加@Autowired
    // 细节3:如果新增final依赖,注解会自动更新构造方法,无需手动修改
    
    // 业务方法示例
    public void createOrder(Long userId) {
        // 直接使用注入的依赖,无需担心空指针(构造器注入保证依赖非空)
        User user = userService.getUserById(userId);
        orderMapper.insertOrder(new Order(user.getId()));
    }
}

对比传统手动写法(凸显注解优势):

java 复制代码
@Service
public class OrderService {
    private final OrderMapper orderMapper;
    private final UserService userService;

    // 手动写构造方法,冗余且容易漏字段
    @Autowired // Spring 4.3+ 可省略,但手动写构造方法必须加
    public OrderService(OrderMapper orderMapper, UserService userService) {
        this.orderMapper = orderMapper;
        this.userService = userService;
    }

    // 业务方法...
}

2. @Data(新手最易踩坑,细节全覆盖)

2.1 底层定义(精准无歧义)

Lombok 的 "组合式注解",核心作用是 "一站式生成 POJO 类的常用模板代码",本质是多个注解的集合,其中隐含 @RequiredArgsConstructor(这是新手最容易忽略的细节,也是踩坑的核心原因)。

2.2 关键细节 1:@Data 包含的所有注解(无遗漏)

@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor,逐个拆解细节:

  • @Getter:为所有字段生成 getter 方法(final 字段仅生成 getter,无 setter);
  • @Setter:为所有 "非 final 字段" 生成 setter 方法(final 字段无 setter,因为 final 字段不可修改);
  • @ToString:生成 toString () 方法,包含所有字段的信息(可通过 exclude 参数排除某些字段);
  • @EqualsAndHashCode:生成 equals () 和 hashCode () 方法,基于所有非 static、非 transient 字段;
  • @RequiredArgsConstructor:生成必需参数构造方法(规则和上文完全一致)------ 这是核心隐含逻辑,也是新手误以为 "@Data 没有构造方法" 的原因。
2.3 关键细节 2:@Data 不包含的注解(避坑核心)

无论什么场景,@Data 都不会自动生成以下两个构造方法,必须手动补充(否则踩坑):

  • ❌ 无参构造方法(@NoArgsConstructor):仅当类中 "没有任何 final/@NonNull 字段" 时,@RequiredArgsConstructor 生成的构造方法会等价于无参构造(特例),否则无参构造会消失;
  • ❌ 全参构造方法(@AllArgsConstructor):无论类中有没有 final/@NonNull 字段,@Data 都不会自动生成全参构造,必须手动添加注解。
2.4 新手最易踩的 3 个坑(细节拉满,附解决方案)

坑点 1:仅加 @Data,有 final/@NonNull 字段 → 无参构造丢失,框架反序列化报错

java 复制代码
// 错误示例:仅加@Data
@Data
public class User {
    @NonNull private String username; // @NonNull字段 → 触发@RequiredArgsConstructor
    private final Integer age = 18; // final字段 → 触发@RequiredArgsConstructor
}

// 测试代码:创建无参对象 → 编译报错
User user = new User(); 
// 报错信息:java: 找不到符号 符号: 方法 User() 位置: 类 com.example.User

原因:@Data 隐含 @RequiredArgsConstructor,生成了 "包含 username" 的构造方法(age 有默认值,不纳入),覆盖了 JVM 默认的无参构造,导致无参构造消失。解决方案:显式添加 @NoArgsConstructor,保证无参构造存在。

坑点 2:仅加 @Data,无 final/@NonNull 字段 → 构造方法等价于无参构造,但无全参构造

java 复制代码
// 示例:类中无final/@NonNull字段
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
}

// 此时@Data生成的构造方法(等价于无参构造)
public User() {}

// 测试:无法通过全参构造创建对象 → 编译报错
User user = new User(1L, "张三", 20); 
// 报错:java: 找不到符号 符号: 方法 User(java.lang.Long,java.lang.String,java.lang.Integer)

原因:@Data 隐含的 @RequiredArgsConstructor,因为没有 "必需字段",所以生成的构造方法无参数(等价于无参构造),但不会生成全参构造。解决方案:如需全参构造,显式添加 @AllArgsConstructor。

坑点 3:@Data 生成的 setter 方法,对 @NonNull 字段有非空校验,新手不知情

java 复制代码
@Data
public class User {
    @NonNull private String username;
}

// 测试:通过setter传入null → 报错
User user = new User("张三");
user.setUsername(null); 
// 报错:java.lang.NullPointerException: username is marked non-null but is null

原因:@Data 包含 @Getter/@Setter,而 @NonNull 字段的 setter 方法会自动添加非空校验,和构造方法的校验逻辑一致。注意:如果不需要 setter 的非空校验,可手动写 setter 方法覆盖(Lombok 不会覆盖手动写的 setter)。

2.5 实体类万能写法(细节拉满,直接复用)

结合所有细节,实体类(POJO/DO/DTO)的标准写法,适配 MyBatis、Jackson 等所有框架,避免所有坑:

java 复制代码
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

/**
 * 实体类标准写法(细节说明):
 * 1. @Data:生成get/set/toString等基础方法,隐含@RequiredArgsConstructor;
 * 2. @NoArgsConstructor:显式生成无参构造,适配框架反序列化(必加);
 * 3. @AllArgsConstructor:显式生成全参构造,按需使用(批量赋值场景);
 * 4. @NonNull:给需要非空的字段加,构造方法和setter会自动校验。
 */
@Data
@NoArgsConstructor // 必加,无论什么场景
@AllArgsConstructor // 按需加,不需要可删除
public class User {
    private Long id; // 普通字段,无校验
    
    @NonNull // 非空字段,构造+setter自动校验
    private String username;
    
    private Integer age; // 普通字段,无校验
    
    // final字段(有默认值)→ 不纳入@RequiredArgsConstructor参数
    private final String defaultRole = "USER";
}

3. @NoArgsConstructor(实体类必加,细节不遗漏)

3.1 底层定义

专门生成 "无参构造方法" 的注解,无任何参数,核心作用是 "适配框架反序列化"(MyBatis、Jackson 等框架必须通过无参构造实例化对象)。

3.2 关键细节(新手易忽略)
  • 细节 1:默认生成的无参构造方法是 public 访问修饰符,可通过参数设置:@NoArgsConstructor(access = AccessLevel.PRIVATE)
  • 细节 2:如果类中有 final 字段,@NoArgsConstructor 生成无参构造的前提是 "final 字段必须有默认值",否则编译报错(违背 final 字段赋值规则);
  • 细节 3:如果手动写了无参构造方法,@NoArgsConstructor 会失效(优先级:手动构造 > 注解生成);
  • 细节 4:@NoArgsConstructor 和 @AllArgsConstructor、@RequiredArgsConstructor 可同时使用,互不冲突(Lombok 会生成多个构造方法)。
3.3 代码示例(含异常场景)
java 复制代码
// 正确示例:final字段有默认值 + @NoArgsConstructor
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class User {
    private final String defaultRole = "USER"; // final有默认值,编译通过
    private Long id;
}

// 错误示例:final字段无默认值 + @NoArgsConstructor → 编译报错
@NoArgsConstructor
public class User {
    private final String defaultRole; // final无默认值,无参构造无法赋值
    private Long id;
}
// 报错:java: 变量 defaultRole 可能尚未初始化

4. @AllArgsConstructor(全参构造,含非空校验细节)

4.1 底层定义

专门生成 "包含类中所有字段" 的全参构造方法,参数顺序 = 字段在类中的声明顺序,核心作用是 "批量赋值"(手动创建对象时,一次性传入所有字段值)。

4.2 关键细节(含非空校验实现)
  • 细节 1:默认无任何参数校验,即使字段加了 @NonNull,也不会自动校验(需手动配合 @NonNull 实现校验,这是核心细节);
  • 细节 2:参数顺序 = 字段声明顺序,和字段类型、注解无关(比如先声明 id,再声明 name,构造参数就是 (id, name));
  • 细节 3:访问修饰符默认是 public,可通过参数设置:@AllArgsConstructor(access = AccessLevel.PRIVATE)
  • 细节 4:如果类中有 final 字段,全参构造方法会为 final 字段赋值(无论 final 字段是否有默认值,都会覆盖默认值);
  • 细节 5:手动写全参构造方法后,@AllArgsConstructor 会失效;
  • 细节 6:非空校验实现:给需要校验的字段加 @NonNull,@AllArgsConstructor 会自动为该字段添加 null 校验(和 @RequiredArgsConstructor 的校验逻辑一致)。
4.3 代码示例(含非空校验)

java

运行

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

// 全参构造 + 非空校验 + 无参构造 + 基础方法
@Data
@NoArgsConstructor
@AllArgsConstructor // 全参构造
public class User {
    private Long id; // 普通字段,无校验
    
    @NonNull // 加@NonNull,全参构造会自动校验null
    private String username;
    
    @NonNull // 加@NonNull,全参构造会自动校验null
    private String phone;
    
    private Integer age; // 普通字段,无校验
}
4.4 等价手动代码(含校验逻辑)
java 复制代码
public class User {
    private Long id;
    @NonNull
    private String username;
    @NonNull
    private String phone;
    private Integer age;

    // 无参构造(@NoArgsConstructor生成)
    public User() {}

    // 全参构造(@AllArgsConstructor生成,含@NonNull校验)
    public User(Long id, String username, String phone, Integer age) {
        // 自动为@NonNull字段添加非空校验
        if (username == null) {
            throw new NullPointerException("username is marked non-null but is null");
        }
        if (phone == null) {
            throw new NullPointerException("phone is marked non-null but is null");
        }
        // 为所有字段赋值(包括final字段,会覆盖默认值)
        this.id = id;
        this.username = username;
        this.phone = phone;
        this.age = age;
    }

    // getter/setter/toString等(@Data生成)
}
4.5 复杂校验场景(自定义提示 / 格式校验)

如果需要自定义非空提示、格式校验(如手机号格式),@AllArgsConstructor + @NonNull 无法满足,需手动写全参构造方法(细节如下):

java 复制代码
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor // 保留无参构造
// 移除@AllArgsConstructor,手动写全参构造
public class User {
    private Long id;
    private String username;
    private String phone;
    private Integer age;

    // 手动写全参构造,实现自定义校验
    public User(Long id, String username, String phone, Integer age) {
        // 1. 自定义非空提示
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空且不能为空白");
        }
        // 2. 手机号格式校验
        if (phone == null) {
            throw new IllegalArgumentException("手机号不能为空");
        }
        String phoneRegex = "^1[3-9]\\d{9}$";
        if (!phone.matches(phoneRegex)) {
            throw new IllegalArgumentException("手机号格式不正确,需为11位有效手机号");
        }
        // 3. 年龄范围校验
        if (age != null && (age < 0 || age > 150)) {
            throw new IllegalArgumentException("年龄需在0-150之间");
        }
        // 4. 赋值(校验通过后)
        this.id = id;
        this.username = username;
        this.phone = phone;
        this.age = age;
    }
}

细节:手动写全参构造后,依然要保留 @NoArgsConstructor,否则框架反序列化报错。

二、核心注解关系与区别(细节对比,不混淆)

2.1 注解包含关系(精准无歧义)

  • @Data 包含 @RequiredArgsConstructor(唯一包含关系);
  • @Data 不包含 @NoArgsConstructor、@AllArgsConstructor(必须手动补充);
  • @NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor 三者互不包含,可同时使用。

2.2 核心区别对比表(细节拉满,一目了然)

注解名 生成构造方法类型 参数包含范围 是否自动非空校验 适用场景 易错点
@RequiredArgsConstructor 必需参数构造 final 字段(无默认值)、@NonNull 字段 是(仅 @NonNull 字段) Spring 服务类(构造器注入) final 有默认值时不纳入参数
@Data 隐含必需参数构造 同 @RequiredArgsConstructor 是(仅 @NonNull 字段) 实体类(POJO/DO/DTO) 无无参 / 全参构造,需手动补充
@NoArgsConstructor 无参构造 无参数 实体类(适配框架反序列化) final 字段无默认值时编译报错
@AllArgsConstructor 全参构造 所有字段(按声明顺序) 仅 @NonNull 字段自动校验 批量赋值场景 默认无校验,需配合 @NonNull

三、实战场景细节(不同类的注解选型,直接复用)

3.1 实体类(POJO/DO/DTO)

核心需求:适配框架反序列化、有基础 get/set 方法、按需批量赋值、非空校验,细节如下:

  • 必加注解:@Data + @NoArgsConstructor(缺一不可);
  • 按需加注解:@AllArgsConstructor(需要全参构造时);
  • 非空校验:给需要非空的字段加 @NonNull(构造 + setter 自动校验);
  • final 字段:如果有默认值,不影响构造方法;如果无默认值,需通过 @RequiredArgsConstructor(@Data 隐含)的构造方法赋值。

标准示例:

java 复制代码
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

@Data
@NoArgsConstructor // 必加
@AllArgsConstructor // 按需加
public class UserDTO {
    private Long id;
    
    @NonNull // 非空校验
    private String username;
    
    @NonNull // 非空校验
    private String phone;
    
    private Integer age;
    
    // final字段(有默认值)
    private final String defaultRole = "USER";
}

3.2 Spring 服务类(@Service/@Controller)

核心需求:构造器注入依赖、代码简洁、无冗余,细节如下:

  • 必加注解:@RequiredArgsConstructor;
  • 依赖字段:必须用 final 修饰(否则无法纳入构造参数,无法注入);
  • 无需加 @Autowired:Spring 4.3+ 会自动识别构造器注入,无需手动加;
  • 避免加 @Data:服务类无需 get/set/toString,加 @Data 会生成冗余方法,仅需 @RequiredArgsConstructor。

标准示例:

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor // 必加
public class UserService {
    // 所有依赖用final修饰,自动构造器注入
    private final UserMapper userMapper;
    private final OrderMapper orderMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 业务方法,直接使用依赖
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

3.3 工具类(无状态,无需注入)

核心需求:无构造方法(或私有构造)、避免实例化,细节如下:

  • 无需加任何构造相关注解;
  • 手动写私有无参构造,避免外部实例化:
java 复制代码
public class StringUtils {
    // 私有无参构造,避免外部实例化
    private StringUtils() {}
    
    // 静态工具方法
    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
}

四、所有易错点汇总(细节全覆盖,避免踩坑)

  1. 易错点 1:仅加 @Data,有 final/@NonNull 字段 → 无参构造丢失,框架反序列化报错 → 解决方案:加 @NoArgsConstructor;
  2. 易错点 2:@Data 包含 @RequiredArgsConstructor,误以为 @Data 有全参构造 → 解决方案:需手动加 @AllArgsConstructor;
  3. 易错点 3:@RequiredArgsConstructor 的参数不包含 "有默认值的 final 字段" → 无需处理,属于正常逻辑;
  4. 易错点 4:@NoArgsConstructor 用于有 final 字段的类,未给 final 字段赋默认值 → 编译报错 → 解决方案:给 final 字段加默认值;
  5. 易错点 5:@AllArgsConstructor 默认无校验,以为加了 @AllArgsConstructor 就有非空校验 → 解决方案:给字段加 @NonNull;
  6. 易错点 6:Spring 服务类的依赖未用 final 修饰,加了 @RequiredArgsConstructor 也无法注入 → 解决方案:依赖字段用 final 修饰;
  7. 易错点 7:手动写了构造方法,注解生成的构造方法失效 → 解决方案:要么删除手动构造,要么保留手动构造(按需选择);
  8. 易错点 8:@NonNull 字段的 setter 方法会自动校验 null,新手不知情导致报错 → 解决方案:避免给 setter 传入 null,或手动写 setter 覆盖校验逻辑。

五、总结(细节闭环,吃透核心)

  1. 核心注解分工:@RequiredArgsConstructor(Spring 注入)、@Data(实体类基础)、@NoArgsConstructor(实体类必加)、@AllArgsConstructor(批量赋值);
  2. @Data 的核心坑:隐含 @RequiredArgsConstructor,无无参 / 全参构造,必须手动补充 @NoArgsConstructor(必加)、@AllArgsConstructor(按需);
  3. 非空校验:@RequiredArgsConstructor、@AllArgsConstructor 配合 @NonNull 实现,复杂校验需手动写构造方法;
  4. 实战选型:实体类(@Data+@NoArgsConstructor±@AllArgsConstructor)、Spring 服务类(@RequiredArgsConstructor+final 依赖);
  5. 所有细节围绕 "避免手动写模板代码、适配框架、避免空指针",记住核心规则,就能避开所有坑,高效使用 Lombok。
相关推荐
Java面试题总结2 小时前
2026最新Java八股文(完整版)
java·开发语言·jvm·数据库·java面试·java八股文
DeepModel2 小时前
【概率分布】卡方分布的原理、推导与实战应用
python·算法·概率论
6+h2 小时前
【java】System类详解
java·开发语言·python
tankeven2 小时前
NxN棋盘问题00:对角线特性
c++·算法
滴滴答滴答答2 小时前
机考刷题之 23&24&25 LeetCode 55&213&123
算法·leetcode·职场和发展
2501_911088232 小时前
C++中的代理模式变体
开发语言·c++·算法
客卿1232 小时前
岛屿问题--bfs的应用--二维网络题目学习
学习·算法·宽度优先
无限进步_2 小时前
【C++】只出现一次的数字 III:位运算的巧妙应用
数据结构·c++·git·算法·leetcode·github·visual studio
予枫的编程笔记2 小时前
【面试专栏|Java并发编程】CAS 核心原理,优缺点,ABA问题与解决方案
java·并发编程·java面试·java并发·aba问题·cas原理·面试干货