JDK21 Pattern Matching for Switch:多态dispatch的未来形态
2026/4/30大约 10 分钟
JDK21 Pattern Matching for Switch:多态dispatch的未来形态
适读人群:做过访问者模式、对Java多态dispatch有深度思考的开发者 | 阅读时长:约16分钟
开篇故事
2022年,我做一个规则引擎重构,有各种条件类型:等于条件、范围条件、正则条件、复合条件(AND/OR)。处理这些条件的代码用了访问者模式,大概写了15个类。
新同事看了直接懵了:"这是在写Java还是在写设计模式教材?"
用JDK21改写之后,整个dispatch逻辑浓缩成一个方法:
static boolean evaluate(Condition condition, Map<String, Object> context) {
return switch (condition) {
case EqualCondition(var field, var value) ->
Objects.equals(context.get(field), value);
case RangeCondition(var field, var min, var max) -> {
var v = (Comparable) context.get(field);
yield v != null && v.compareTo(min) >= 0 && v.compareTo(max) <= 0;
}
case RegexCondition(var field, var pattern) -> {
var v = String.valueOf(context.get(field));
yield pattern.matcher(v).matches();
}
case AndCondition(var conditions) ->
conditions.stream().allMatch(c -> evaluate(c, context));
case OrCondition(var conditions) ->
conditions.stream().anyMatch(c -> evaluate(c, context));
};
}这就是Pattern Matching for Switch的力量:把分散在多个类里的dispatch逻辑集中在一处,可读性和可维护性都大幅提升。
一、Pattern Matching for Switch的设计背景
1.1 版本时间线
JDK17:Preview(JEP 406)
JDK18:第二次Preview
JDK19:第三次Preview
JDK20:第四次Preview
JDK21:正式GA(2023年9月,JEP 441)经历了4次Preview,JDK21才正式GA,可见这个特性设计了多少细节。
1.2 解决了什么问题
传统多态dispatch的问题:
方式1:instanceof链(难维护)
if (x instanceof A) { ((A)x).doA(); }
else if (x instanceof B) { ((B)x).doB(); }
// ...
方式2:访问者模式(代码量大,难理解)
interface Visitor<R> { R visit(A a); R visit(B b); ... }
// 每个子类实现accept(visitor)
// 每种处理逻辑实现一个Visitor类
方式3:虚方法(把逻辑散在各子类里)
class A { void process() { ... } }
class B { void process() { ... } }
// 可以,但把"处理逻辑"耦合进了数据类Pattern Matching for Switch的解决方案:
- 集中处理:一个switch处理所有类型
- 编译期穷举检查(配合sealed classes)
- 类型安全,无需强转
- 支持guard条件(when)
- 支持null处理
二、Pattern Matching for Switch深度解析
2.1 完整语法
switch (value) {
// 类型Pattern:匹配类型并绑定
case TypeA a -> expression;
// 带guard的类型Pattern
case TypeB b when b.getX() > 0 -> expression;
// Record Pattern(解构)
case RecordType(var x, var y) -> expression;
// 带guard的Record Pattern
case RecordType(var x, var y) when x > 0 -> expression;
// null处理(JDK21新增)
case null -> expression;
// default(处理未匹配的情况)
default -> expression;
// null和default合并
case null, default -> expression;
}2.2 穷举性检查规则
switch是表达式时,必须穷举:
对于sealed类型:
- 如果覆盖了所有permitted子类,不需要default
- 如果漏掉任何子类,编译错误
对于普通类/接口:
- 必须有default(因为子类未知)
对于null:
- 默认不处理null(传入null会NPE)
- 需要显式 case null 才处理Mermaid图(Pattern Matching for Switch流程):
三、完整代码示例
3.1 规则引擎实现
import java.util.*;
import java.util.regex.*;
import java.util.stream.*;
/**
* Pattern Matching for Switch完整示例:规则引擎
* 引入版本:JDK17 Preview;GA版本:JDK21(2023年9月,JEP 441)
*/
public class RuleEngineDemo {
// 条件类型(sealed + record)
sealed interface Condition
permits EqualCondition, RangeCondition, RegexCondition,
NotCondition, AndCondition, OrCondition {}
record EqualCondition(String field, Object value) implements Condition {}
record RangeCondition(String field, Comparable<?> min, Comparable<?> max) implements Condition {}
record RegexCondition(String field, Pattern pattern) implements Condition {
RegexCondition(String field, String regex) {
this(field, Pattern.compile(regex));
}
}
record NotCondition(Condition inner) implements Condition {}
record AndCondition(List<Condition> conditions) implements Condition {}
record OrCondition(List<Condition> conditions) implements Condition {}
// ===== 条件求值(switch pattern matching)=====
@SuppressWarnings("unchecked")
static boolean evaluate(Condition condition, Map<String, Object> ctx) {
return switch (condition) {
case EqualCondition(var field, var value) ->
Objects.equals(ctx.get(field), value);
case RangeCondition(var field, var min, var max) -> {
var v = ctx.get(field);
if (v == null) yield false;
@SuppressWarnings("rawtypes")
var comparable = (Comparable) v;
yield comparable.compareTo(min) >= 0 && comparable.compareTo(max) <= 0;
}
case RegexCondition(var field, var pattern) -> {
var v = ctx.get(field);
yield v != null && pattern.matcher(v.toString()).matches();
}
case NotCondition(var inner) ->
!evaluate(inner, ctx);
case AndCondition(var conditions) ->
conditions.stream().allMatch(c -> evaluate(c, ctx));
case OrCondition(var conditions) ->
conditions.stream().anyMatch(c -> evaluate(c, ctx));
};
}
// ===== 条件描述(展示同一数据结构多种处理)=====
static String describe(Condition condition) {
return switch (condition) {
case EqualCondition(var field, var value) ->
field + " == " + value;
case RangeCondition(var field, var min, var max) ->
min + " <= " + field + " <= " + max;
case RegexCondition(var field, var pattern) ->
field + " matches /" + pattern.pattern() + "/";
case NotCondition(var inner) ->
"NOT(" + describe(inner) + ")";
case AndCondition(var conditions) ->
"(" + conditions.stream().map(RuleEngineDemo::describe)
.collect(Collectors.joining(" AND ")) + ")";
case OrCondition(var conditions) ->
"(" + conditions.stream().map(RuleEngineDemo::describe)
.collect(Collectors.joining(" OR ")) + ")";
};
}
// ===== 条件优化(简化冗余条件)=====
static Condition optimize(Condition condition) {
return switch (condition) {
// 化简:NOT(NOT(x)) = x
case NotCondition(NotCondition(var inner)) -> optimize(inner);
// 化简:AND只有一个条件 = 该条件
case AndCondition(var conditions) when conditions.size() == 1 ->
optimize(conditions.get(0));
// 化简:OR只有一个条件 = 该条件
case OrCondition(var conditions) when conditions.size() == 1 ->
optimize(conditions.get(0));
// 递归优化子条件
case AndCondition(var conditions) ->
new AndCondition(conditions.stream().map(RuleEngineDemo::optimize).toList());
case OrCondition(var conditions) ->
new OrCondition(conditions.stream().map(RuleEngineDemo::optimize).toList());
case NotCondition(var inner) ->
new NotCondition(optimize(inner));
default -> condition; // 基本条件无需优化
};
}
public static void main(String[] args) {
// 构建规则:年龄18-60,邮件格式正确,不是黑名单用户
var rule = new AndCondition(List.of(
new RangeCondition("age", 18, 60),
new RegexCondition("email", "^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$"),
new NotCondition(new EqualCondition("status", "BLACKLISTED"))
));
System.out.println("规则: " + describe(rule));
var validUser = Map.<String, Object>of("age", 25, "email", "alice@example.com", "status", "ACTIVE");
var tooYoung = Map.<String, Object>of("age", 16, "email", "bob@example.com", "status", "ACTIVE");
var blacklisted = Map.<String, Object>of("age", 30, "email", "charlie@evil.com", "status", "BLACKLISTED");
System.out.println("有效用户: " + evaluate(rule, validUser)); // true
System.out.println("未成年: " + evaluate(rule, tooYoung)); // false
System.out.println("黑名单: " + evaluate(rule, blacklisted)); // false
// 测试优化
var redundant = new NotCondition(new NotCondition(new EqualCondition("x", 1)));
System.out.println("优化前: " + describe(redundant));
System.out.println("优化后: " + describe(optimize(redundant)));
}
}3.2 表达式求值器(展示多态dispatch)
/**
* 完整的表达式求值器
* 展示Pattern Matching替代传统访问者模式
*/
public class ExpressionEvaluatorV2 {
sealed interface Expr
permits Literal, Variable, BinaryOp, UnaryOp, Conditional {}
record Literal(double value) implements Expr {}
record Variable(String name) implements Expr {}
record BinaryOp(Expr left, String op, Expr right) implements Expr {}
record UnaryOp(String op, Expr operand) implements Expr {}
record Conditional(Expr condition, Expr then, Expr otherwise) implements Expr {}
// ===== 旧写法:访问者模式(需要很多类)=====
// interface ExprVisitor<T> {
// T visit(Literal literal);
// T visit(Variable variable);
// T visit(BinaryOp binaryOp);
// T visit(UnaryOp unaryOp);
// T visit(Conditional conditional);
// }
// 每种操作(求值、格式化、优化)需要实现一个Visitor类
// 代码量N倍增加
// ===== 新写法:Pattern Matching for Switch =====
// 求值
static double eval(Expr expr, Map<String, Double> env) {
return switch (expr) {
case Literal(var value) -> value;
case Variable(var name) -> {
if (!env.containsKey(name))
throw new RuntimeException("Undefined: " + name);
yield env.get(name);
}
case BinaryOp(var left, var op, var right) -> {
double l = eval(left, env), r = eval(right, env);
yield switch (op) {
case "+" -> l + r;
case "-" -> l - r;
case "*" -> l * r;
case "/" -> { if (r == 0) throw new ArithmeticException("div by zero"); yield l / r; }
case "%" -> l % r;
case "^" -> Math.pow(l, r);
case "<" -> l < r ? 1.0 : 0.0;
case ">" -> l > r ? 1.0 : 0.0;
case "==" -> l == r ? 1.0 : 0.0;
default -> throw new RuntimeException("Unknown op: " + op);
};
}
case UnaryOp(var op, var operand) -> {
double v = eval(operand, env);
yield switch (op) {
case "-" -> -v;
case "!" -> v == 0 ? 1.0 : 0.0;
case "abs" -> Math.abs(v);
case "sqrt" -> Math.sqrt(v);
default -> throw new RuntimeException("Unknown unary op: " + op);
};
}
case Conditional(var cond, var then, var otherwise) ->
eval(cond, env) != 0 ? eval(then, env) : eval(otherwise, env);
};
}
// 格式化(另一种处理,无需新的Visitor类)
static String format(Expr expr) {
return switch (expr) {
case Literal(var value) ->
value == Math.floor(value) ? String.valueOf((long) value) : String.valueOf(value);
case Variable(var name) -> name;
case BinaryOp(var left, var op, var right) ->
"(" + format(left) + " " + op + " " + format(right) + ")";
case UnaryOp(var op, var operand) ->
op + "(" + format(operand) + ")";
case Conditional(var cond, var then, var otherwise) ->
format(cond) + " ? " + format(then) + " : " + format(otherwise);
};
}
// 常量折叠优化(第三种处理)
static Expr foldConstants(Expr expr) {
return switch (expr) {
// 递归优化子表达式
case BinaryOp(var left, var op, var right) -> {
var foldedLeft = foldConstants(left);
var foldedRight = foldConstants(right);
// 如果两边都是字面量,直接计算
if (foldedLeft instanceof Literal(var l) && foldedRight instanceof Literal(var r)) {
var result = eval(new BinaryOp(foldedLeft, op, foldedRight), Map.of());
yield new Literal(result);
}
// x + 0 = x, x * 1 = x
if (foldedRight instanceof Literal(var r) && r == 0 && "+".equals(op)) yield foldedLeft;
if (foldedRight instanceof Literal(var r) && r == 1 && "*".equals(op)) yield foldedLeft;
yield new BinaryOp(foldedLeft, op, foldedRight);
}
case UnaryOp(var op, var operand) -> {
var folded = foldConstants(operand);
if (folded instanceof Literal(var v)) {
yield new Literal(eval(new UnaryOp(op, folded), Map.of()));
}
yield new UnaryOp(op, folded);
}
default -> expr; // Literal和Variable无需优化
};
}
public static void main(String[] args) {
// 构建表达式: (x + 2) * (y - 1)
var expr = new BinaryOp(
new BinaryOp(new Variable("x"), "+", new Literal(2)),
"*",
new BinaryOp(new Variable("y"), "-", new Literal(1))
);
var env = Map.of("x", 3.0, "y", 5.0);
System.out.println("表达式: " + format(expr));
System.out.println("求值(x=3,y=5): " + eval(expr, env)); // (3+2)*(5-1) = 20
// 常量折叠
var constExpr = new BinaryOp(
new BinaryOp(new Literal(2), "+", new Literal(3)),
"*",
new BinaryOp(new Variable("x"), "+", new Literal(0))
);
System.out.println("折叠前: " + format(constExpr));
System.out.println("折叠后: " + format(foldConstants(constExpr)));
// 折叠后: (5 * x)(2+3=5,x+0=x)
// 条件表达式
var conditional = new Conditional(
new BinaryOp(new Variable("x"), ">", new Literal(0)),
new Variable("x"),
new UnaryOp("-", new Variable("x")) // abs(x)
);
System.out.println("abs(x) expr: " + format(conditional));
System.out.println("abs(3): " + eval(conditional, Map.of("x", 3.0)));
System.out.println("abs(-3): " + eval(conditional, Map.of("x", -3.0)));
}
}四、踩坑实录
坑1:Guard条件的顺序决定结果
// 注意:case按从上到下顺序匹配,第一个匹配成功的case执行
Object obj = 42;
String result = switch (obj) {
// 错误顺序:这个会匹配所有Integer,下面的case不可达
case Integer i -> "integer: " + i; // 太宽泛!
case Integer i when i > 0 -> "positive"; // 不可达!编译警告
default -> "other";
};
// 编译器警告:case is dominated by a preceding case
// 正确顺序:更具体的条件在前
String result2 = switch (obj) {
case Integer i when i < 0 -> "negative: " + i; // 具体的
case Integer i when i == 0 -> "zero";
case Integer i -> "positive: " + i; // 最宽泛的在最后
default -> "other";
};坑2:sealed类的穷举 vs null处理
sealed interface Foo permits Bar, Baz {}
record Bar(int x) implements Foo {}
record Baz(String s) implements Foo {}
Foo foo = null; // 实际上Foo不能是null,但引用可以是null
// 问题:即使覆盖了所有permitted子类,null仍然会NPE
try {
String result = switch (foo) { // NPE!
case Bar(var x) -> "bar:" + x;
case Baz(var s) -> "baz:" + s;
};
} catch (NullPointerException e) {
System.out.println("NPE!");
}
// 正确:处理null
String safe = switch (foo) {
case null -> "null"; // 明确处理null
case Bar(var x) -> "bar:" + x;
case Baz(var s) -> "baz:" + s;
};坑3:Dominance(某个case使另一个不可达)
// 编译器会检测并报警告/错误
interface Animal {}
class Dog implements Animal { public void bark() {} }
Object obj = new Dog();
// 编译错误:Object case会匹配所有,后面的Dog不可达
// String s = switch (obj) {
// case Object o -> "object"; // 太宽泛
// case Dog d -> "dog"; // 不可达!
// };
// 正确:具体类型在前
String s = switch (obj) {
case Dog d -> "dog";
case Animal a -> "animal";
default -> "other";
};坑4:default vs 覆盖所有类型
sealed interface Shape permits Circle, Rectangle {}
record Circle(double r) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
// 有了sealed,下面可以不写default:
double area(Shape shape) {
return switch (shape) {
case Circle(var r) -> Math.PI * r * r;
case Rectangle(var w, var h) -> w * h;
// 不需要default!编译器知道只有这两种
};
}
// 注意:如果用Object类型而不是Shape,必须有default
double areaFromObject(Object obj) {
return switch (obj) {
case Circle(var r) -> Math.PI * r * r;
case Rectangle(var w, var h) -> w * h;
default -> throw new IllegalArgumentException("Not a shape: " + obj);
// 必须有default!
};
}坑5:switch表达式里yield的正确用法
// 在switch表达式的代码块里,必须用yield返回值,不能用return
int x = 5;
String result = switch (x) {
case 1 -> "one";
case 5 -> {
System.out.println("Processing five");
// return "five"; // 编译错误!
yield "five"; // 正确:yield返回switch表达式的值
}
default -> "other";
};
// switch语句(不是表达式)里,代码块里可以用break
// 但如果是switch语句,不返回值,所以没有yield
void processSwitch(int x) {
switch (x) {
case 1 -> System.out.println("one");
case 5 -> {
System.out.println("Processing five");
break; // switch语句里可以break(也可以不写,箭头case自动不fall-through)
}
default -> System.out.println("other");
}
}五、总结与延伸
5.1 Pattern Matching for Switch的核心价值
传统方式的问题:
instanceof链 → 代码冗长,容易漏处理
访问者模式 → 代码量爆炸,难理解
虚方法 → 把处理逻辑耦合进数据类
Pattern Matching for Switch的解决:
1. 集中dispatch:一个switch搞定所有类型
2. 编译期穷举:配合sealed,漏处理编译报错
3. 类型安全:无需强转
4. Guard条件:细粒度控制
5. 解构访问:直接拿到字段值(配合Record Pattern)5.2 适合的场景
✓ 强烈推荐:
- 代数数据类型处理(sealed + record体系)
- 规则引擎、表达式求值器
- AST解析和转换
- 事件处理(不同类型事件不同处理)
- JSON类型系统处理
✓ 一般推荐:
- 替代多个if-instanceof链
- 类型分发处理
✗ 不推荐:
- 只有一两种类型(过度设计)
- 运行时动态注册类型的场景(用策略模式更好)5.3 版本兼容建议
- JDK21(LTS):Pattern Matching for Switch GA,可用于生产
- JDK17(LTS):只有基础的instanceof Pattern Matching,没有switch Pattern
- 迁移策略:升级到JDK21后,可以逐步把instanceof链和访问者模式重构为switch Pattern
