本章学习目标

  1. 掌握 Record 类型的原理与最佳实践
  2. 理解密封类的设计意图与应用场景
  3. 熟练运用 switch 模式匹配与 Record Patterns

2.1 Record:告别样板代码

Java 中大量类只是数据载体——字段、构造器、getter、equals、hashCode、toString。Record 将这一切浓缩为一行声明。

// Java 17+:Record 声明
public record Point(double x, double y) {}

// 编译器自动生成:
// - private final double x, y
// - 全参构造器 Point(double x, double y)
// - public double x(), public double y() (注意:不是 getX())
// - equals(), hashCode(), toString()

Record 的使用

Point p1 = new Point(1.0, 2.0);
Point p2 = new Point(1.0, 2.0);

System.out.println(p1.x());           // 1.0
System.out.println(p1.equals(p2));    // true(基于值比较)
System.out.println(p1);               // Point[x=1.0, y=2.0]

Record 的约束

约束 说明
字段不可变 所有字段都是 private final
不能继承 Record 隐式继承 java.lang.Record
可以实现接口 record Pair(int a, int b) implements Comparable<Pair>
可以添加自定义方法 可以写业务方法,但不能修改字段
可以添加紧凑构造器 用于参数校验

紧凑构造器

public record Range(int start, int end) {
    // 紧凑构造器:省略参数列表(与默认构造器签名一致时)
    public Range {
        if (start > end) throw new IllegalArgumentException(
            "start(" + start + ") 不能大于 end(" + end + ")");
    }

    // 业务方法
    public int size() { return end - start; }
    public boolean contains(int value) { return value >= start && value < end; }
}

2.2 Sealed Classes:控制继承层次

在面向对象设计中,有时你希望精确控制哪些类可以继承某个接口或抽象类。密封类提供了这种能力。

public sealed interface Shape
    permits Circle, Rectangle, Triangle {
    double area();
}

// 子类必须是 final、sealed 或 non-sealed 之一
public record Circle(double radius) implements Shape {
    @Override public double area() { return Math.PI * radius * radius; }
}

public record Rectangle(double width, double height) implements Shape {
    @Override public double area() { return width * height; }
}

public final class Triangle implements Shape {
    private final double a, b, c;
    public Triangle(double a, double b, double c) {
        this.a = a; this.b = b; this.c = c;
    }
    @Override public double area() {
        double s = (a + b + c) / 2;
        return Math.sqrt(s * (s-a) * (s-b) * (s-c));
    }
}

子类修饰符要求

修饰符 含义
final 终结继承链,不能再被继承
sealed 继续限制,必须声明自己的 permits
non-sealed 开放继承,任何人都可以继承这个子类

2.3 Pattern Matching for switch

Java 21 将模式匹配从 instanceof 扩展到 switch,并与密封类完美结合。

// Java 21+:switch 模式匹配
public static String describe(Shape shape) {
    return switch (shape) {
        case Circle c    -> "圆形,半径: " + c.radius();
        case Rectangle r -> "矩形,面积: " + r.area();
        case Triangle t  -> "三角形,面积: " + t.area();
        // 编译器知道 Shape 只有这三个子类型
        // 无需 default 分支!(密封类的完整性检查)
    };
}

带条件的模式匹配(Guarded Patterns)

public static String classify(Object obj) {
    return switch (obj) {
        case Integer i when i > 0  -> "正整数: " + i;
        case Integer i when i == 0 -> "零";
        case Integer i             -> "负整数: " + i;
        case String s when s.isEmpty() -> "空字符串";
        case String s              -> "字符串: " + s.substring(0, Math.min(s.length(), 20));
        case null                  -> "null";
        default                    -> "未知类型: " + obj.getClass().getName();
    };
}

2.4 Record Patterns(记录模式)

Java 21 允许在 switch 中解构 Record,直接访问其字段。

public record Address(String city, String street) {}
public record Person(String name, Address address) {}

public static String format(Person person) {
    return switch (person) {
        // 解构 Record:直接绑定 name 和 address 的字段
        case Person(var name, Address(var city, var street)) ->
            name + " 住在 " + city + " " + street;
    };
}

// 嵌套解构
public record Line(Point start, Point end) {}

public static double length(Line line) {
    return switch (line) {
        case Line(Point(var x1, var y1), Point(var x2, var y2)) ->
            Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
    };
}

2.5 数据导向编程(Data-Oriented Programming)

Record + Sealed Class + Pattern Matching 三者组合形成了一种新的编程范式——数据导向编程

  1. Record 定义数据形状
  2. Sealed Class 定义数据变体
  3. Pattern Matching 根据数据形状分派逻辑
// 用数据导向编程处理 JSON-like 结构
sealed interface JsonValue permits JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {}

record JsonString(String value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonBoolean(boolean value) implements JsonValue {}
record JsonNull() implements JsonValue {}
record JsonArray(List<JsonValue> elements) implements JsonValue {}
record JsonObject(Map<String, JsonValue> fields) implements JsonValue {}

// 递归处理
public static String prettyPrint(JsonValue value, int indent) {
    return switch (value) {
        case JsonString s  -> """ + s.value() + """;
        case JsonNumber n  -> String.valueOf(n.value());
        case JsonBoolean b -> String.valueOf(b.value());
        case JsonNull n    -> "null";
        case JsonArray a   -> "[...]";  // 省略递归逻辑
        case JsonObject o  -> "{...}";  // 省略递归逻辑
    };
}

2.6 常见陷阱

⚠️ 陷阱 1:Record 的访问器方法名不是 getX(),而是 x()

Point p = new Point(1, 2);
p.x();   // ✅ 正确
p.getX(); // ❌ 编译错误

⚠️ 陷阱 2:密封类的 permits 列表必须与子类在同一模块/包中

  • 如果密封类和子类在同一个模块中,permits 可以省略(编译器自动推断)
  • 如果跨模块,必须显式声明

⚠️ 陷阱 3:switch 模式匹配中,case null 必须显式处理

  • 旧版 switch 遇到 null 会抛 NPE
  • 新版 switch 必须写 case null -> ... 或使用 default 兜底

2.7 小结

Record、Sealed Class 和 Pattern Matching 是 Java 类型系统的三大增强。它们共同构成了"数据导向编程"的基础,让 Java 能够像 Kotlin 的 data class + when 表达式一样优雅地处理数据分派逻辑。

2.8 面试实战题

题目 1:Record 与普通 POJO 类有什么区别?什么时候用 Record?

参考答案
核心区别:

  1. 不可变性:Record 的字段都是 final 的,POJO 可以是可变的
  2. 样板代码:Record 自动生成构造器、equals、hashCode、toString
  3. 继承限制:Record 隐式继承 java.lang.Record,不能再继承其他类
  4. 访问器命名:Record 用 x() 而非 getX()

使用场景:

  • 适合 Record:DTO、值对象、配置类、Map 的 Key
  • 不适合 Record:JPA Entity(需要可变字段和无参构造器)、需要继承的类

题目 2:密封类解决了什么问题?请举一个实际应用场景。

参考答案
密封类解决的问题:限制继承层次,让编译器能穷举所有子类型。

实际场景——支付系统中的支付方式:

sealed interface PaymentMethod permits CreditCard, Alipay, WechatPay {}

支付系统只支持这 3 种方式,新增支付方式需要显式修改密封类,而不是随意扩展。配合 switch 模式匹配,编译器可以确保所有支付方式都被处理。

题目 3:Pattern Matching for switch 相比传统 switch 有什么优势?

参考答案

  1. 类型安全:传统 switch 只能匹配基本类型和 String/Enum,新 switch 可以匹配任意对象类型
  2. 变量绑定case Circle c -> 自动完成类型检查和变量绑定,无需手动转型
  3. 完整性检查:对密封类,编译器能检查 switch 是否覆盖了所有子类型,遗漏会报编译错误
  4. Guarded Patternscase Integer i when i > 0 支持条件判断,减少嵌套

题目 4:什么是数据导向编程?与面向对象编程有什么区别?

参考答案
数据导向编程(DOP)是一种将数据形状行为分离的编程范式:

  • OOP:数据和行为封装在同一个类中(shape.area()
  • DOP:数据用 Record/Sealed Class 定义形状,行为用 switch 模式匹配实现

DOP 的优势:

  1. 新增数据变体容易:添加新的 Record 类型,switch 会编译报错提醒
  2. 新增操作容易:添加新的 switch 方法,不影响数据定义
  3. 更适合数据处理:JSON 解析、协议处理等场景,DOP 更清晰

题目 5:Record Patterns 如何实现嵌套解构?

参考答案

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Line line = new Line(new Point(0, 0), new Point(3, 4));

// 嵌套解构:一层 case 提取所有字段
switch (line) {
    case Line(Point(var x1, var y1), Point(var x2, var y2)) -> {
        double distance = Math.sqrt(Math.pow(x2-x1,2) + Math.pow(y2-y1,2));
        System.out.println("距离: " + distance);
    }
}

Record Patterns 在匹配时自动调用 accessor 方法(line.start()start.x()),将嵌套结构展开为扁平变量。这是一种"声明式解构",比手动逐层调用更清晰。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐