JDK21模式匹配完全指南:instanceof增强与switch模式匹配
2026/4/30大约 8 分钟
JDK21模式匹配完全指南:instanceof增强与switch模式匹配
适读人群:Java中高级开发者、关注现代Java语法特性的后端工程师 | 阅读时长:约16分钟 | 文章类型:特性详解+实战
开篇故事
做Code Review时,我经常看到这样的代码:
Object obj = getResponse();
if (obj instanceof String) {
String s = (String) obj; // 先判断类型,再强转
if (s.length() > 10) {
System.out.println(s.toUpperCase());
}
}这段代码没有问题,但啰嗦——先判断是String,再强转成String,这两步在逻辑上是重复的。
JDK14预览、JDK16正式引入的instanceof模式匹配,把这两步合并了:
if (obj instanceof String s && s.length() > 10) {
System.out.println(s.toUpperCase()); // s直接可用
}这只是开始。JDK21的switch模式匹配,把这个能力推向了极致。今天把模式匹配完整地讲一遍。
一、instanceof的模式匹配
基本语法
// 旧写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// 新写法(JDK16+)
if (obj instanceof String s) {
System.out.println(s.length()); // s已经是String类型
}作用域规则
// 注意:s的作用域只在模式匹配成功的分支里
if (obj instanceof String s) {
System.out.println(s); // OK
}
// System.out.println(s); // 编译错误:s超出作用域
// 配合&&:两个条件都成立时,s才绑定
if (obj instanceof String s && s.length() > 5) {
System.out.println(s); // OK,s已绑定且长度>5
}
// 配合||:不能用s,因为||时s可能没绑定
// if (obj instanceof String s || s.length() > 5) { // 编译错误二、switch的模式匹配(JDK21正式GA)
基本用法
// JDK17以前:switch只能匹配基本类型、String、枚举
switch (obj) {
case 1: System.out.println("整数1"); break;
case "hello": System.out.println("字符串"); break;
default: System.out.println("其他");
}
// JDK21:switch支持任意类型的模式匹配
Object obj = getObj();
String result = switch (obj) {
case Integer i -> "整数: " + i;
case String s when s.length() > 10 -> "长字符串: " + s;
case String s -> "短字符串: " + s;
case Double d -> "浮点数: " + d;
case null -> "null值";
default -> "其他类型";
};when守卫(Guard)
when子句添加额外的条件:
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.isBlank() -> "空字符串";
case String s -> "字符串: " + s;
default -> "其他";
};
}三、核心原理深挖
配合sealed interface实现完整性检查
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
// 编译器能检查完整性:如果漏了某个子类,编译报错
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> {
double s = (t.a() + t.b() + t.c()) / 2;
yield Math.sqrt(s * (s - t.a()) * (s - t.b()) * (s - t.c()));
}
// 不需要default:sealed interface保证子类是有限的,编译器能验证完整性
};
}Record模式解构(JDK21)
// Record模式:直接解构Record的字段
record Point(int x, int y) {}
record Line(Point from, Point to) {}
void describe(Object obj) {
switch (obj) {
case Point(int x, int y) when x == y ->
System.out.println("对角线点: (" + x + ", " + y + ")");
case Point(int x, int y) ->
System.out.println("普通点: (" + x + ", " + y + ")");
case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
System.out.printf("线段: (%d,%d) -> (%d,%d)%n", x1, y1, x2, y2);
default ->
System.out.println("未知: " + obj);
}
}四、完整代码实现
代码一:模式匹配的各种用法
package com.laozhang.patternmatching;
/**
* JDK21模式匹配完整演示
*/
public class PatternMatchingDemo {
// ===== 类型层次(sealed interface + record)=====
sealed interface ApiResponse permits SuccessResponse, ErrorResponse, PaginatedResponse {}
record SuccessResponse(Object data) implements ApiResponse {}
record ErrorResponse(int code, String message) implements ApiResponse {}
record PaginatedResponse(Object data, int total, int page) implements ApiResponse {}
// ===== 1. 传统做法(重构前)=====
static String handleOld(ApiResponse response) {
if (response instanceof SuccessResponse) {
SuccessResponse success = (SuccessResponse) response;
return "成功: " + success.data();
} else if (response instanceof ErrorResponse) {
ErrorResponse error = (ErrorResponse) response;
return "错误 " + error.code() + ": " + error.message();
} else if (response instanceof PaginatedResponse) {
PaginatedResponse paged = (PaginatedResponse) response;
return "分页数据, 共" + paged.total() + "条, 第" + paged.page() + "页";
} else {
return "未知响应";
}
}
// ===== 2. 模式匹配(重构后)=====
static String handleNew(ApiResponse response) {
return switch (response) {
case SuccessResponse(var data) -> "成功: " + data;
case ErrorResponse(int code, String message) when code >= 500 ->
"服务器错误 " + code + ": " + message;
case ErrorResponse(int code, String message) ->
"客户端错误 " + code + ": " + message;
case PaginatedResponse(var data, int total, int page) ->
"分页数据, 共" + total + "条, 第" + page + "页";
};
// 注意:不需要default,sealed保证完整性,编译器会检查
}
// ===== 3. 复杂嵌套场景 =====
sealed interface Expr permits Num, Add, Mul, Neg {}
record Num(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
record Neg(Expr expr) implements Expr {}
static int eval(Expr expr) {
return switch (expr) {
case Num(int value) -> value;
case Add(Expr left, Expr right) -> eval(left) + eval(right);
case Mul(Expr left, Expr right) -> eval(left) * eval(right);
case Neg(Expr e) -> -eval(e);
};
}
static String prettyPrint(Expr expr) {
return switch (expr) {
case Num(int v) -> String.valueOf(v);
case Add(Expr l, Expr r) -> "(" + prettyPrint(l) + " + " + prettyPrint(r) + ")";
case Mul(Expr l, Expr r) -> "(" + prettyPrint(l) + " * " + prettyPrint(r) + ")";
case Neg(Expr e) -> "(-" + prettyPrint(e) + ")";
};
}
// ===== 4. null处理 =====
static String describeObject(Object obj) {
return switch (obj) {
case null -> "null";
case Integer i when i < 0 -> "负整数: " + i;
case Integer i -> "正整数或零: " + i;
case String s when s.isEmpty() -> "空字符串";
case String s -> "字符串: " + s;
case int[] arr -> "int数组, 长度: " + arr.length;
default -> "其他: " + obj.getClass().getSimpleName();
};
}
public static void main(String[] args) {
System.out.println("=== ApiResponse处理 ===");
ApiResponse[] responses = {
new SuccessResponse("订单数据"),
new ErrorResponse(404, "资源不存在"),
new ErrorResponse(503, "服务暂时不可用"),
new PaginatedResponse("用户列表", 100, 1)
};
for (ApiResponse r : responses) {
System.out.println(handleOld(r) + " (旧)");
System.out.println(handleNew(r) + " (新)");
System.out.println();
}
System.out.println("=== 表达式求值 ===");
// (2 + 3) * (-4)
Expr expr = new Mul(new Add(new Num(2), new Num(3)), new Neg(new Num(4)));
System.out.println("表达式: " + prettyPrint(expr));
System.out.println("结果: " + eval(expr)); // -20
System.out.println("\n=== null安全处理 ===");
Object[] objs = {null, -5, 42, "", "hello", new int[]{1, 2, 3}, 3.14};
for (Object o : objs) {
System.out.println(describeObject(o));
}
}
}代码二:模式匹配在实际业务中的应用
package com.laozhang.patternmatching;
import java.util.List;
/**
* 模式匹配在业务代码中的实际应用
* 事件处理、消息路由、结果类型处理
*/
public class PatternMatchingBusiness {
// ===== 业务场景:领域事件处理 =====
sealed interface DomainEvent permits
OrderCreated, OrderPaid, OrderShipped, OrderCancelled {}
record OrderCreated(String orderId, String userId, long amount) implements DomainEvent {}
record OrderPaid(String orderId, String paymentMethod) implements DomainEvent {}
record OrderShipped(String orderId, String trackingNo) implements DomainEvent {}
record OrderCancelled(String orderId, String reason) implements DomainEvent {}
// 事件处理器(模式匹配替代冗长的if-else链)
static void handleEvent(DomainEvent event) {
switch (event) {
case OrderCreated(var orderId, var userId, long amount) when amount > 10000 ->
System.out.printf("[VIP订单] %s 用户 %s 下单 %.2f 元,触发人工审核%n",
orderId, userId, amount / 100.0);
case OrderCreated(var orderId, var userId, var amount) ->
System.out.printf("[普通订单] %s 用户 %s 下单%n", orderId, userId);
case OrderPaid(var orderId, var method) ->
System.out.printf("[支付完成] %s 通过 %s 支付%n", orderId, method);
case OrderShipped(var orderId, var tracking) ->
System.out.printf("[已发货] %s 运单号 %s%n", orderId, tracking);
case OrderCancelled(var orderId, var reason) ->
System.out.printf("[已取消] %s 原因: %s%n", orderId, reason);
}
}
// ===== 业务场景:Result类型(消除异常驱动编程)=====
sealed interface Result<T> permits Result.Success, Result.Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error, Exception cause) implements Result<T> {}
static Result<String> fetchUserName(Long userId) {
if (userId <= 0) {
return new Failure<>("用户ID无效: " + userId, null);
}
// 模拟成功
return new Success<>("张三(ID:" + userId + ")");
}
static void processUser(Long userId) {
Result<String> result = fetchUserName(userId);
switch (result) {
case Success<String>(var name) ->
System.out.println("获取用户成功: " + name);
case Failure<String>(var error, var cause) when cause != null ->
System.out.println("获取用户失败(有异常): " + error + ", " + cause.getMessage());
case Failure<String>(var error, var cause) ->
System.out.println("获取用户失败: " + error);
}
}
public static void main(String[] args) {
System.out.println("=== 领域事件处理 ===");
List<DomainEvent> events = List.of(
new OrderCreated("ORD001", "USR001", 150000L), // 1500元VIP订单
new OrderCreated("ORD002", "USR002", 5000L), // 50元普通订单
new OrderPaid("ORD001", "支付宝"),
new OrderShipped("ORD002", "SF-12345678"),
new OrderCancelled("ORD001", "用户主动取消")
);
events.forEach(PatternMatchingBusiness::handleEvent);
System.out.println("\n=== Result类型处理 ===");
processUser(1L); // 成功
processUser(-1L); // 失败
System.out.println("\n=== 模式匹配的价值总结 ===");
System.out.println("1. 消除重复的instanceof+强转");
System.out.println("2. sealed interface确保处理所有情况");
System.out.println("3. when守卫使条件表达更清晰");
System.out.println("4. Record解构避免调用getter");
}
}四、踩坑实录
坑1:switch模式匹配要求考虑null,但老代码没考虑
报错现象:
java.lang.NullPointerException: Cannot invoke switch on null value根本原因:
JDK21之前,switch不支持null,传入null直接NPE。JDK21的switch模式匹配支持case null,但需要显式处理。
具体解法:
// 必须显式处理null
String result = switch (obj) {
case null -> "null"; // 加上这一行
case String s -> "字符串";
default -> "其他";
};坑2:sealed interface的子类列表不完整,switch编译失败
报错现象:
error: the switch expression does not cover all possible input values根本原因:
switch配合sealed interface时,编译器会检查是否覆盖了所有子类。新加了一个子类但没有更新switch语句,编译失败。
具体解法:
这个报错其实是好事——sealed interface + switch的组合能让编译器帮你检查完整性,不会遗漏新加的类型。按编译器提示补充对应的case即可。
坑3:when守卫的顺序很重要,更具体的case要放前面
触发代码:
// 错误顺序:宽泛的case在前,具体的case永远到不了
String classify(int n) {
return switch (n) {
case int i -> "整数: " + i; // 这一条匹配所有int
case int i when i < 0 -> "负整数: " + i; // 永远不会执行!
default -> "其他";
};
}具体解法:
更具体的(有when守卫的)case放在前面:
String classify(int n) {
return switch (n) {
case int i when i < 0 -> "负整数: " + i; // 先匹配具体的
case int i when i == 0 -> "零";
case int i -> "正整数: " + i; // 再匹配宽泛的
};
}五、总结与延伸
模式匹配是Java向表达式式编程迈出的重要一步。
进化路线:
- JDK14预览 / JDK16 GA:
instanceof的模式匹配 - JDK17预览 / JDK21 GA:switch的模式匹配 + Record解构
- JDK21:sealed interface + switch = 完整的代数数据类型
适用场景:
- 处理多种响应类型(API Response、Result类型)
- 领域事件路由和处理
- AST/表达式树的递归计算
- 替代传统的if-else instanceof链
如果你的项目已经在用JDK21,建议把所有if (x instanceof A) { A a = (A) x; ... }替换成if (x instanceof A a),这是无风险的小重构,代码更简洁。
