14、JDK 23 新特性:模式匹配与记录类组合应用:实战中的模式匹配最佳实践

写Java代码的时候,经常需要处理不同类型的数据,传统方式得用instanceof判断类型,然后强制转换,代码写起来麻烦,还容易出错。现在有了模式匹配和记录类,可以更优雅地处理这些场景,特别是它们组合使用的时候,代码简洁多了,逻辑也更清晰。

鹏磊我之前做项目的时候,经常要处理各种类型的数据,比如解析JSON、处理不同格式的消息、处理不同类型的错误,传统方式得写一堆if-else,每个分支都得判断类型、转换类型,代码冗长,还容易漏掉某些情况。

现在有了模式匹配和记录类,可以用switch表达式配合记录类,直接解构数据,代码简洁,编译器还能检查是否覆盖了所有情况。特别是配合密封类(Sealed Classes)用,效果更好,类型安全,代码也清晰。

模式匹配与记录类基础

记录类(Record)的特点

记录类是Java 14引入的特性,主要用于存储不可变数据。它自动生成构造函数、访问器方法、equals、hashCode和toString方法。

记录类的特点:

  1. 不可变性:记录类的字段默认是final的,不可变
  2. 自动生成方法:自动生成构造函数、访问器、equals、hashCode、toString
  3. 简洁语法:声明简洁,减少样板代码
  4. 解构支持:支持模式匹配解构
// 传统方式,用普通类
public class Point {
    private final int x;
    private final int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int x() { return x; }
    public int y() { return y; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
    
    @Override
    public String toString() {
        return "Point{x=" + x + ", y=" + y + "}";
    }
}

// 记录类方式,简洁多了
public record Point(int x, int y) {
    // 自动生成所有方法
}

记录类让代码简洁多了,特别是存储数据的时候。

模式匹配(Pattern Matching)的特点

模式匹配允许在代码中更直观地检查和解构对象。在Java中,模式匹配被引入到instanceof和switch表达式中。

模式匹配的特点:

  1. 类型检查和解构:同时进行类型检查和变量绑定
  2. 减少样板代码:不需要显式的类型转换
  3. 编译器检查:编译器可以检查是否覆盖所有情况
  4. 配合密封类:与密封类配合使用,类型更安全
// 传统方式,用instanceof和强制转换
if (obj instanceof Point) {
    Point point = (Point) obj;  // 需要强制转换
    int x = point.x();
    int y = point.y();
    // 使用x和y
}

// 模式匹配方式,直接解构
if (obj instanceof Point(int x, int y)) {  // 直接解构
    // 直接使用x和y,不需要强制转换
    System.out.println("x=" + x + ", y=" + y);
}

模式匹配让代码更简洁,逻辑也更清晰。

组合应用场景

场景1:处理不同类型的消息

在处理消息系统的时候,经常要处理不同类型的消息:

// 定义消息类型
public sealed interface Message permits TextMessage, ImageMessage, VideoMessage {
}

public record TextMessage(String content, String sender) implements Message {
}

public record ImageMessage(String url, int width, int height) implements Message {
}

public record VideoMessage(String url, int duration, String format) implements Message {
}

// 处理消息,用模式匹配和记录类
public void processMessage(Message message) {
    String result = switch (message) {
        case TextMessage(String content, String sender) -> {
            // 直接解构TextMessage,获取content和sender
            yield "Text: " + content + " from " + sender;
        }
        case ImageMessage(String url, int width, int height) -> {
            // 直接解构ImageMessage,获取url、width和height
            yield "Image: " + url + " (" + width + "x" + height + ")";
        }
        case VideoMessage(String url, int duration, String format) -> {
            // 直接解构VideoMessage,获取url、duration和format
            yield "Video: " + url + " (" + duration + "s, " + format + ")";
        }
    };
    System.out.println(result);
}

这样写代码简洁,编译器还能检查是否覆盖了所有情况。

场景2:处理不同类型的错误

在处理错误的时候,经常要处理不同类型的错误:

// 定义错误类型
public sealed interface AppError permits ValidationError, NetworkError, DatabaseError {
    String message();
}

public record ValidationError(String message, String field) implements AppError {
}

public record NetworkError(String message, int statusCode) implements AppError {
}

public record DatabaseError(String message, String sql) implements AppError {
}

// 处理错误,用模式匹配和记录类
public void handleError(AppError error) {
    switch (error) {
        case ValidationError(String msg, String field) -> {
            // 直接解构ValidationError
            System.out.println("Validation error in field " + field + ": " + msg);
        }
        case NetworkError(String msg, int statusCode) -> {
            // 直接解构NetworkError
            System.out.println("Network error (status " + statusCode + "): " + msg);
        }
        case DatabaseError(String msg, String sql) -> {
            // 直接解构DatabaseError
            System.out.println("Database error in SQL: " + sql + " - " + msg);
        }
    }
}

这样处理错误代码清晰,每种错误类型都能直接解构。

场景3:解析JSON数据

在解析JSON数据的时候,经常要处理不同类型的节点:

// 定义JSON节点类型
public sealed interface JsonNode permits JsonString, JsonNumber, JsonObject, JsonArray {
}

public record JsonString(String value) implements JsonNode {
}

public record JsonNumber(double value) implements JsonNode {
}

public record JsonObject(Map<String, JsonNode> fields) implements JsonNode {
}

public record JsonArray(List<JsonNode> elements) implements JsonNode {
}

// 解析JSON,用模式匹配和记录类
public String formatJson(JsonNode node) {
    return switch (node) {
        case JsonString(String value) -> "\"" + value + "\"";  // 直接解构
        case JsonNumber(double value) -> String.valueOf(value);  // 直接解构
        case JsonObject(Map<String, JsonNode> fields) -> {
            // 直接解构JsonObject
            String result = fields.entrySet().stream()
                .map(e -> "\"" + e.getKey() + "\": " + formatJson(e.getValue()))
                .collect(Collectors.joining(", "));
            yield "{" + result + "}";
        }
        case JsonArray(List<JsonNode> elements) -> {
            // 直接解构JsonArray
            String result = elements.stream()
                .map(this::formatJson)
                .collect(Collectors.joining(", "));
            yield "[" + result + "]";
        }
    };
}

这样解析JSON代码简洁,逻辑也清晰。

场景4:处理表达式树

在处理表达式树的时候,经常要处理不同类型的表达式:

// 定义表达式类型
public sealed interface Expr permits Constant, Variable, Add, Multiply {
}

public record Constant(int value) implements Expr {
}

public record Variable(String name) implements Expr {
}

public record Add(Expr left, Expr right) implements Expr {
}

public record Multiply(Expr left, Expr right) implements Expr {
}

// 计算表达式,用模式匹配和记录类
public int evaluate(Expr expr, Map<String, Integer> vars) {
    return switch (expr) {
        case Constant(int value) -> value;  // 直接解构Constant
        case Variable(String name) -> vars.getOrDefault(name, 0);  // 直接解构Variable
        case Add(Expr left, Expr right) -> {
            // 直接解构Add,递归计算
            yield evaluate(left, vars) + evaluate(right, vars);
        }
        case Multiply(Expr left, Expr right) -> {
            // 直接解构Multiply,递归计算
            yield evaluate(left, vars) * evaluate(right, vars);
        }
    };
}

这样处理表达式树代码简洁,递归逻辑也清晰。

高级应用技巧

技巧1:嵌套模式匹配

可以在模式匹配中嵌套使用模式匹配:

// 嵌套模式匹配
public String processNested(Message message) {
    return switch (message) {
        case TextMessage(String content, String sender) when sender.startsWith("admin") -> {
            // 带守卫的模式匹配
            yield "Admin message: " + content;
        }
        case TextMessage(String content, String sender) -> {
            // 普通文本消息
            yield "User message: " + content;
        }
        case ImageMessage(String url, int width, int height) when width > 1920 -> {
            // 带守卫的模式匹配,检查宽度
            yield "Large image: " + url;
        }
        case ImageMessage(String url, int width, int height) -> {
            // 普通图片消息
            yield "Image: " + url;
        }
        case VideoMessage(String url, int duration, String format) -> {
            // 视频消息
            yield "Video: " + url;
        }
    };
}

嵌套模式匹配让代码更灵活,可以处理更复杂的场景。

技巧2:使用守卫条件

可以在模式匹配中使用守卫条件(when子句):

// 使用守卫条件
public String processWithGuard(Message message) {
    return switch (message) {
        case TextMessage(String content, String sender) 
            when content.length() > 100 -> {
            // 长消息
            yield "Long text: " + content.substring(0, 100) + "...";
        }
        case TextMessage(String content, String sender) -> {
            // 短消息
            yield "Text: " + content;
        }
        case ImageMessage(String url, int width, int height) 
            when width == height -> {
            // 正方形图片
            yield "Square image: " + url;
        }
        case ImageMessage(String url, int width, int height) -> {
            // 普通图片
            yield "Image: " + url;
        }
        case VideoMessage(String url, int duration, String format) 
            when duration > 60 -> {
            // 长视频
            yield "Long video: " + url;
        }
        case VideoMessage(String url, int duration, String format) -> {
            // 短视频
            yield "Video: " + url;
        }
    };
}

守卫条件让模式匹配更灵活,可以处理更复杂的条件。

技巧3:部分解构

可以只解构需要的字段,忽略不需要的:

// 部分解构,使用_忽略不需要的字段
public String processPartial(Message message) {
    return switch (message) {
        case TextMessage(String content, _) -> {
            // 只解构content,忽略sender
            yield "Text: " + content;
        }
        case ImageMessage(String url, _, _) -> {
            // 只解构url,忽略width和height
            yield "Image: " + url;
        }
        case VideoMessage(String url, int duration, _) -> {
            // 只解构url和duration,忽略format
            yield "Video: " + url + " (" + duration + "s)";
        }
    };
}

部分解构让代码更简洁,只关注需要的字段。

技巧4:类型推断

模式匹配支持类型推断,可以简化代码:

// 类型推断
public void processWithInference(Object obj) {
    switch (obj) {
        case Point(int x, int y) -> {
            // 类型推断,不需要显式声明类型
            System.out.println("Point: (" + x + ", " + y + ")");
        }
        case String s -> {
            // 简单类型匹配
            System.out.println("String: " + s);
        }
        case Integer i when i > 0 -> {
            // 带守卫的简单类型匹配
            System.out.println("Positive integer: " + i);
        }
        default -> {
            System.out.println("Other: " + obj);
        }
    }
}

类型推断让代码更简洁,减少冗余的类型声明。

最佳实践

1. 使用密封类确保类型安全

配合密封类使用,确保类型安全:

// 好的做法:使用密封类
public sealed interface Result<T> permits Success, Failure {
}

public record Success<T>(T value) implements Result<T> {
}

public record Failure<T>(String error) implements Result<T> {
}

// 编译器会检查是否覆盖所有情况
public <T> String processResult(Result<T> result) {
    return switch (result) {
        case Success<T>(T value) -> "Success: " + value;
        case Failure<T>(String error) -> "Failure: " + error;
        // 编译器确保覆盖了所有情况
    };
}

密封类确保类型安全,编译器会检查是否覆盖所有情况。

2. 保持记录类简洁

记录类应该保持简洁,只包含数据:

// 好的做法:记录类只包含数据
public record User(String name, String email, int age) {
    // 可以添加验证方法
    public User {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
}

// 不好的做法:记录类包含太多逻辑
public record User(String name, String email, int age) {
    public void sendEmail() {  // 不应该在记录类中
        // ...
    }
}

记录类应该只包含数据,逻辑应该放在其他地方。

3. 使用有意义的变量名

在模式匹配中使用有意义的变量名:

// 好的做法:使用有意义的变量名
case TextMessage(String content, String sender) -> {
    // content和sender有意义
}

// 不好的做法:使用无意义的变量名
case TextMessage(String a, String b) -> {
    // a和b没有意义
}

有意义的变量名让代码更易读。

4. 处理所有情况

确保处理所有可能的情况:

// 好的做法:处理所有情况
public String process(Message message) {
    return switch (message) {
        case TextMessage(String content, String sender) -> "Text";
        case ImageMessage(String url, int width, int height) -> "Image";
        case VideoMessage(String url, int duration, String format) -> "Video";
        // 如果使用密封类,编译器会检查是否覆盖所有情况
    };
}

// 不好的做法:遗漏某些情况
public String process(Message message) {
    if (message instanceof TextMessage) {
        return "Text";
    }
    // 遗漏了ImageMessage和VideoMessage
    return "Unknown";
}

处理所有情况确保代码的健壮性。

性能考虑

模式匹配的性能

模式匹配在编译时会优化,性能通常不输传统方式:

// 模式匹配方式,编译器会优化
public String process(Message message) {
    return switch (message) {
        case TextMessage(String content, String sender) -> "Text";
        case ImageMessage(String url, int width, int height) -> "Image";
        case VideoMessage(String url, int duration, String format) -> "Video";
    };
}

// 传统方式,性能可能不如模式匹配
public String process(Message message) {
    if (message instanceof TextMessage) {
        TextMessage tm = (TextMessage) message;
        return "Text";
    } else if (message instanceof ImageMessage) {
        ImageMessage im = (ImageMessage) message;
        return "Image";
    } else if (message instanceof VideoMessage) {
        VideoMessage vm = (VideoMessage) message;
        return "Video";
    }
    return "Unknown";
}

模式匹配在编译时会优化,性能通常更好。

记录类的性能

记录类的性能通常不输普通类:

// 记录类,性能通常不输普通类
public record Point(int x, int y) {
}

// 普通类,性能可能不如记录类
public class Point {
    private final int x;
    private final int y;
    // ...
}

记录类在编译时会优化,性能通常不差。

总结

模式匹配和记录类的组合使用,确实让Java代码变得更简洁、更安全了。特别是配合密封类使用,类型安全,编译器还能检查是否覆盖所有情况,代码质量更高。

鹏磊我觉得这个组合特别适合处理那些需要根据不同类型做不同处理的场景,比如消息处理、错误处理、数据解析这些。用模式匹配和记录类,代码简洁,逻辑清晰,而且类型安全。

总的来说,模式匹配和记录类是Java现代化的重要特性,它们的组合使用让Java代码更优雅、更安全。在实际开发中,应该多使用这些特性,提高代码质量。

本文章最后更新于 2025-11-28