> ## Documentation Index
> Fetch the complete documentation index at: https://blog.pig4cloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 别再混淆了！Spring @Value 读配置，Lombok @Value 造不可变类，一文搞懂两者区别

> 全面解析 Java 中实现不可变对象的两种主流方式：Records 和 Lombok @Value 注解，包含实战示例和最佳实践

在现代 Java 开发中，不可变性（Immutability）已成为构建健壮、线程安全应用的核心原则之一。本文将深入探讨如何使用 Java Records 和 Lombok 的 `@Value` 注解来优雅地实现不可变对象。

## 什么是不可变对象？

不可变对象是指一旦创建完成，其内部状态就无法被修改的对象。在 Java 中，不可变对象需要满足以下特征：

* **状态固定**：对象创建后，所有字段的值保持不变
* **字段不可变**：所有字段都使用 `final` 关键字修饰
* **无修改方法**：不提供任何 setter 方法或其他可以修改内部状态的方法
* **防御性拷贝**：如果包含可变对象的引用，不直接暴露这些引用

### 常见的不可变对象示例

Java 标准库中有许多经典的不可变类：

```java theme={null}
String name = "张三";  // String 是不可变的
Integer count = 100;   // 包装类都是不可变的
LocalDate today = LocalDate.now();  // 日期时间 API 都是不可变的
```

## 为什么不可变性如此重要？

### 1. 线程安全性

不可变对象天生就是线程安全的。由于状态永远不会改变，多个线程可以同时访问同一个对象而不需要任何同步机制。

```java theme={null}
// 多线程环境下安全使用
public record User(String name, int age) {}

User user = new User("李四", 25);
// 多个线程可以安全地读取 user，无需担心并发问题
```

### 2. 简化代码逻辑

不可变对象消除了意外状态变化的可能性，使代码更容易理解和维护。你不需要追踪对象在程序运行过程中的状态变化。

### 3. 适合作为 HashMap 的键

不可变对象的哈希码永远不会改变，这使它们非常适合作为 `HashMap` 或 `HashSet` 的键。

```java theme={null}
Map<User, String> userMap = new HashMap<>();
User user = new User("王五", 30);
userMap.put(user, "员工信息");
// user 的哈希码永远不会改变，保证了映射的可靠性
```

### 4. 避免防御性拷贝

当方法返回不可变对象时，不需要创建防御性拷贝，因为调用者无法修改返回的对象。

## 传统方式实现不可变类

在 Java 16 之前，我们需要手动编写大量样板代码来实现不可变类：

```java theme={null}
public final class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
```

这种方式虽然可行，但存在明显的问题：

* 代码冗长，样板代码过多
* 容易出错（比如忘记将类或字段声明为 final）
* 维护成本高，增加字段时需要修改多个方法

## 使用 Java Records 实现不可变性

Java 14 引入的 Records 为定义不可变数据类提供了一种简洁优雅的方式。

### 基本用法

```java theme={null}
public record Person(String name, int age) {}
```

就是这么简单！这一行代码自动生成了：

* 私有的 final 字段
* 公共的构造函数
* getter 方法（`name()` 和 `age()`）
* `equals()`、`hashCode()` 和 `toString()` 方法

### Records 的特性

```java theme={null}
public record Employee(String name, int age, String department) {
    
    // 紧凑构造函数：用于参数验证
    public Employee {
        if (age < 18) {
            throw new IllegalArgumentException("员工年龄必须大于等于 18 岁");
        }
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("姓名不能为空");
        }
    }
    
    // 可以添加自定义方法
    public boolean isRetirementAge() {
        return age >= 60;
    }
    
    // 可以添加静态工厂方法
    public static Employee of(String name, int age) {
        return new Employee(name, age, "未分配");
    }
}
```

### 使用示例

```java theme={null}
// 创建 Record 实例
Employee emp = new Employee("张三", 28, "技术部");

// 访问字段（注意：getter 方法名是字段名，不是 getXxx）
System.out.println(emp.name());        // 输出: 张三
System.out.println(emp.age());         // 输出: 28
System.out.println(emp.department());  // 输出: 技术部

// 自动生成的 toString()
System.out.println(emp);  // 输出: Employee[name=张三, age=28, department=技术部]

// equals() 和 hashCode() 基于所有字段
Employee emp2 = new Employee("张三", 28, "技术部");
System.out.println(emp.equals(emp2));  // 输出: true
```

### 处理可变字段

当 Record 包含可变对象时，需要特别注意：

```java theme={null}
public record Department(String name, List<String> employees) {
    
    // 使用紧凑构造函数创建防御性拷贝
    public Department {
        employees = List.copyOf(employees);  // 创建不可变副本
    }
    
    // 或者使用规范构造函数
    public Department(String name, List<String> employees) {
        this.name = name;
        this.employees = Collections.unmodifiableList(new ArrayList<>(employees));
    }
}
```

## 使用 Lombok @Value 实现不可变性

Lombok 是一个强大的 Java 库，通过注解自动生成样板代码。`@Value` 注解专门用于创建不可变类。

### 添加 Lombok 依赖

首先在项目中添加 Lombok 依赖：

```xml theme={null}
<!-- Maven -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>
```

### 基本用法

```java theme={null}
import lombok.Value;

@Value
public class Person {
    String name;
    int age;
}
```

`@Value` 注解会自动：

* 将类声明为 `final`
* 将所有字段声明为 `private final`
* 生成全参构造函数
* 生成 getter 方法（`getName()`、`getAge()`）
* 生成 `equals()`、`hashCode()` 和 `toString()` 方法

### @Value 的高级特性

```java theme={null}
import lombok.Value;
import lombok.With;

@Value
public class Employee {
    String name;
    int age;
    String department;
    
    // @With 注解生成 withXxx 方法，用于创建修改后的副本
    @With
    String email;
    
    // 可以自定义方法
    public boolean isRetirementAge() {
        return age >= 60;
    }
}
```

### 使用 @With 创建修改副本

```java theme={null}
Employee emp = new Employee("李四", 30, "销售部", "lisi@example.com");

// 创建一个新对象，只修改 email 字段
Employee updatedEmp = emp.withEmail("lisi_new@example.com");

System.out.println(emp.getEmail());        // 输出: lisi@example.com
System.out.println(updatedEmp.getEmail()); // 输出: lisi_new@example.com
```

### 自定义构造函数

```java theme={null}
import lombok.Value;

@Value
public class Product {
    String name;
    double price;
    
    // 自定义构造函数进行验证
    public Product(String name, double price) {
        if (price < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
        this.name = name;
        this.price = price;
    }
}
```

### 处理集合字段

```java theme={null}
import lombok.Value;
import lombok.Singular;

import java.util.List;

@Value
public class Team {
    String name;
    
    // @Singular 注解提供构建器模式支持
    @Singular
    List<String> members;
}

// 使用构建器（需要配合 @Builder 使用）
import lombok.Builder;

@Value
@Builder
public class Team {
    String name;
    
    @Singular
    List<String> members;
}

// 使用示例
Team team = Team.builder()
    .name("开发团队")
    .member("张三")
    .member("李四")
    .member("王五")
    .build();
```
