本文目录导读:

Java 14 Records 完整案例
Records 是 Java 14 引入的预览特性,用于创建不可变的数据载体类,下面通过多个案例详细展示其用法。
基础用法
// 最简单的 Record 定义
public record Point(int x, int y) {}
// 使用示例
public class BasicRecordExample {
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
// 自动生成的方法
System.out.println("x: " + p1.x()); // 访问器方法
System.out.println("y: " + p1.y());
System.out.println("toString: " + p1);
System.out.println("hashCode: " + p1.hashCode());
System.out.println("equals: " + p1.equals(p2)); // true
// 解构(Java 21+ 支持)
// int x = p1.x();
// int y = p1.y();
}
}
带自定义构造器的 Record
// 带验证逻辑的 Record
public record Person(String name, int age) {
// 紧凑构造器(Compact Constructor)
public Person {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be null or blank");
}
// 可以修改参数值
name = name.trim();
}
// 额外的静态工厂方法
public static Person of(String fullName) {
String[] parts = fullName.split(" ");
return new Person(parts[0], Integer.parseInt(parts[1]));
}
// 额外的实例方法
public boolean isAdult() {
return age >= 18;
}
// 重写访问器
@Override
public String name() {
return "Name: " + name;
}
}
包含集合的 Record
import java.util.ArrayList;
import java.util.List;
// 注意:Record 的字段是 final,但集合内容可变
public record Course(String name, List<String> students) {
// 防御性拷贝构造函数
public Course {
students = List.copyOf(students); // 返回不可变列表
}
// 添加学生(返回新实例)
public Course addStudent(String student) {
List<String> newStudents = new ArrayList<>(students);
newStudents.add(student);
return new Course(name, newStudents);
}
}
public class RecordWithCollection {
public static void main(String[] args) {
Course course = new Course("Java Programming",
new ArrayList<>(List.of("Alice", "Bob")));
// 尝试修改集合会抛出异常
try {
course.students().add("Charlie");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify immutable list");
}
// 通过方法添加
Course updated = course.addStudent("Charlie");
System.out.println(updated);
}
}
Record 与泛型
// 泛型 Record
public record Pair<K, V>(K key, V value) {
static <K, V> Pair<K, V> of(K key, V value) {
return new Pair<>(key, value);
}
}
// 带类型约束的 Record
public record NumberBox<T extends Number>(T value) {
public double doubleValue() {
return value.doubleValue();
}
}
public class GenericRecordExample {
public static void main(String[] args) {
Pair<String, Integer> pair = Pair.of("age", 25);
System.out.println(pair);
NumberBox<Integer> intBox = new NumberBox<>(42);
System.out.println("Double value: " + intBox.doubleValue());
// NumberBox<String> box = new NumberBox<>("test"); // 编译错误
}
}
Record 实现接口
// 定义接口
interface Shape {
double area();
default String describe() {
return "Shape with area: " + area();
}
}
// Record 实现接口
public record Circle(double radius) implements Shape {
public Circle {
if (radius <= 0) {
throw new IllegalArgumentException("Radius must be positive");
}
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public String describe() {
return "Circle with radius " + radius + ", area: " + area();
}
}
public record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
}
// 使用示例
public class RecordInterfaceExample {
public static void main(String[] args) {
Shape circle = new Circle(5.0);
Shape rectangle = new Rectangle(4.0, 6.0);
System.out.println(circle.describe());
System.out.println(rectangle.describe());
// 多态使用
List<Shape> shapes = List.of(circle, rectangle);
shapes.forEach(s -> System.out.println("Area: " + s.area()));
}
}
Record 与序列化
import java.io.*;
// Record 天然支持序列化
public record Employee(String id, String name, String department)
implements Serializable {
// 版本序列化 ID
private static final long serialVersionUID = 1L;
// 序列化时的验证
public Employee {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("ID cannot be empty");
}
}
}
public class RecordSerializationExample {
public static void main(String[] args) throws Exception {
Employee employee = new Employee("001", "张三", "IT");
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("employee.ser"))) {
oos.writeObject(employee);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("employee.ser"))) {
Employee deserialized = (Employee) ois.readObject();
System.out.println("Deserialized: " + deserialized);
System.out.println("Equals original: " +
deserialized.equals(employee));
}
}
}
实际应用场景 - 数据传输对象(DTO)
import java.time.LocalDateTime;
import java.util.Map;
// API 响应 DTO
public record ApiResponse<T>(
int status,
String message,
T data,
LocalDateTime timestamp
) {
// 静态工厂方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data, LocalDateTime.now());
}
public static <T> ApiResponse<T> error(int status, String message) {
return new ApiResponse<>(status, message, null, LocalDateTime.now());
}
// 检查是否成功
public boolean isSuccess() {
return status >= 200 && status < 300;
}
}
// 用户请求 DTO
public record UserCreateRequest(
String username,
String email,
String password,
Map<String, String> metadata
) {
// 紧凑构造器验证
public UserCreateRequest {
if (username == null || username.isBlank()) {
throw new IllegalArgumentException("Username is required");
}
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("Password too short");
}
// 防御性拷贝
metadata = metadata == null ? Map.of() : Map.copyOf(metadata);
}
// 脱敏密码方法
public String maskedPassword() {
return "*".repeat(password.length());
}
}
// 使用示例
public class DTOExample {
public static void main(String[] args) {
// 创建用户请求
UserCreateRequest request = new UserCreateRequest(
"john_doe",
"john@example.com",
"password123",
Map.of("role", "admin")
);
System.out.println("Username: " + request.username());
System.out.println("Password: " + request.maskedPassword());
// 创建响应
ApiResponse<UserCreateRequest> success =
ApiResponse.success(request);
ApiResponse<String> error =
ApiResponse.error(400, "Bad Request");
System.out.println(success);
System.out.println("Is success: " + success.isSuccess());
System.out.println(error);
}
}
Record 与 Stream 结合使用
import java.util.List;
import java.util.stream.Collectors;
public record Product(String name, double price, String category) {}
public class RecordWithStream {
public static void main(String[] args) {
List<Product> products = List.of(
new Product("Laptop", 1299.99, "Electronics"),
new Product("Keyboard", 99.99, "Electronics"),
new Product("Book", 19.99, "Books"),
new Product("Monitor", 499.99, "Electronics"),
new Product("Headphones", 199.99, "Accessories")
);
// 分组统计
Map<String, List<Product>> byCategory = products.stream()
.collect(Collectors.groupingBy(Product::category));
System.out.println("Products by category:");
byCategory.forEach((cat, list) ->
System.out.println(cat + ": " + list.size() + " items"));
// 过滤和转换
List<String> expensiveProductNames = products.stream()
.filter(p -> p.price() > 100)
.map(Product::name)
.sorted()
.collect(Collectors.toList());
System.out.println("\nExpensive products: " + expensiveProductNames);
// 计算平均价格
double avgPrice = products.stream()
.mapToDouble(Product::price)
.average()
.orElse(0);
System.out.println("Average price: $" + String.format("%.2f", avgPrice));
}
}
嵌套 Record
// 嵌套 Record 示例
public record Order(
String orderId,
Customer customer,
Address shippingAddress,
List<OrderItem> items
) {
// 嵌套的 Record
public record Customer(String id, String name, String email) {}
public record Address(String street, String city, String zipCode) {}
public record OrderItem(String productId, int quantity, double price) {
public double totalPrice() {
return price * quantity;
}
}
// 计算订单总额
public double totalAmount() {
return items.stream()
.mapToDouble(OrderItem::totalPrice)
.sum();
}
// 自定义 toString
@Override
public String toString() {
return String.format("Order[%s] - Customer: %s - Total: $%.2f",
orderId, customer().name(), totalAmount());
}
}
public class NestedRecordExample {
public static void main(String[] args) {
Order.Customer customer = new Order.Customer("C001", "Alice", "alice@email.com");
Order.Address address = new Order.Address("123 Main St", "Springfield", "12345");
List<Order.OrderItem> items = List.of(
new Order.OrderItem("P001", 2, 25.99),
new Order.OrderItem("P002", 1, 99.99)
);
Order order = new Order("ORD-2024-001", customer, address, items);
System.out.println(order);
System.out.println("Total: $" + String.format("%.2f", order.totalAmount()));
}
}
Record 的最佳实践示例
import java.util.Objects;
import java.util.regex.Pattern;
// 综合示例:展示 Record 的最佳实践
public class BestPracticesExample {
// 1. 使用静态工厂方法
public record Email(String value) {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public Email {
Objects.requireNonNull(value, "Email cannot be null");
if (!EMAIL_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid email: " + value);
}
value = value.toLowerCase();
}
// 静态工厂
public static Email of(String value) {
return new Email(value);
}
}
// 2. 使用记录作为配置对象
public record AppConfig(
String appName,
int port,
boolean debug,
String databaseUrl
) {
// 默认配置
public static AppConfig defaultConfig() {
return new AppConfig("MyApp", 8080, false, "jdbc:mysql://localhost:3306/mydb");
}
// 从环境变量创建
public static AppConfig fromEnv() {
return new AppConfig(
System.getenv().getOrDefault("APP_NAME", "MyApp"),
Integer.parseInt(System.getenv().getOrDefault("PORT", "8080")),
Boolean.parseBoolean(System.getenv().getOrDefault("DEBUG", "false")),
System.getenv().getOrDefault("DB_URL", "jdbc:mysql://localhost:3306/mydb")
);
}
}
// 3. 使用 Record 表示不可变的数据结构
public record Money(String currency, BigDecimal amount) {
public Money {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
}
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(currency, amount.add(other.amount));
}
@Override
public String toString() {
return String.format("%s %.2f", currency, amount);
}
}
public static void main(String[] args) {
// 测试 Email
Email email = Email.of("User@Example.com");
System.out.println("Valid email: " + email.value());
// 测试配置
AppConfig config = AppConfig.defaultConfig();
System.out.println("Config: " + config);
// 测试 Money
Money price1 = new Money("USD", new BigDecimal("19.99"));
Money price2 = new Money("USD", new BigDecimal("4.99"));
Money total = price1.add(price2);
System.out.println("Price 1: " + price1);
System.out.println("Price 2: " + price2);
System.out.println("Total: " + total);
}
}
注意事项
- Record 的字段是 final 的,只能在构造器中赋值
- Record 是隐式 final 的,不能被继承
- Record 不能声明实例字段,但可以声明静态字段
- Record 自动生成 equals(), hashCode(), toString(), 访问器方法
- 紧凑构造器可以修改参数值
- 可以对 Record 进行防御性拷贝来保护不可变性
- Record 适合作为 DTO、值对象、配置对象等
这些案例涵盖了 Java 14 Records 的主要使用场景和最佳实践。