1. 概述
在模式匹配出现之前,switch
语句只能对选择器表达式进行简单的精确常量值匹配。Java SE 21 正式引入了 switch
表达式和语句的模式匹配功能(JEP 441)。模式匹配为定义 switch
选择器和条件提供了更大的灵活性。
我们可以在 case
标签中使用模式(如 Integer i
、String s
)。此外,选择器表达式现在支持任意引用类型(如 ArrayList
、Stack
),而不仅限于原有类型。我们还能在 case
标签中匹配 null
,并通过 when
子句实现条件匹配——这种带 when
的模式称为带守卫的模式匹配。
Java SE 24 进一步将基本类型模式(如 int i
、long l
)作为预览特性加入 case
标签支持。
本文将深入探讨如何在 switch
中使用模式匹配,并分析几个关键特性:值覆盖、子类顺序处理和 null
值处理。
2. Switch 语句基础
在 Java 中,switch
是一种控制流语句,用于将控制权转移到多个预定义的 case
分支之一。switch
语句根据选择器表达式的值匹配对应的 case
标签。
在早期 Java 版本中,选择器表达式只能是数字、字符串或枚举常量,且 case
标签只能包含常量:
final String b = "B";
switch (args[0]) {
case "A" -> System.out.println("Parameter is A");
case b -> System.out.println("Parameter is b");
default -> System.out.println("Parameter is unknown");
};
踩坑提醒:如果变量
b
不是final
,编译器会抛出"需要常量表达式"错误。
3. 模式匹配基础
模式匹配最初作为预览特性出现在 Java SE 14。类型模式由类型名和用于绑定结果的变量组成。将类型模式应用于 instanceof
操作符可简化类型检查和转换,让我们能将两者合并为单一表达式:
if (o instanceof String s) {
System.out.printf("Object is a string %s", s);
} else if (o instanceof Number n) {
System.out.printf("Object is a number %n", n);
}
这种语言增强特性帮助我们用更少的代码写出更易读的程序。instanceof
的模式匹配 在 Java SE 16 转为正式特性。
4. Switch 中的模式匹配
Java SE 17 引入了 switch
表达式和语句的模式匹配,后续版本持续优化,最终在 Java SE 21 成为正式特性。
4.1. 类型模式
看如何在 switch
中应用类型模式。假设我们要用 if-else
实现一个方法,将不同类型转换为 double
:
static double getDoubleUsingIf(Object o) {
double result;
if (o instanceof Integer) {
result = ((Integer) o).doubleValue();
} else if (o instanceof Float) {
result = ((Float) o).doubleValue();
} else if (o instanceof String) {
result = Double.parseDouble(((String) o));
} else {
result = 0d;
}
return result;
}
用类型模式重写后,代码更简洁:
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0d;
};
}
关键改进:早期 Java 中选择器表达式仅支持整数基本类型(除
long
外)、其包装类、枚举和String
。现在类型模式让选择器表达式支持任意引用类型。
4.2. 带守卫的模式
类型模式能基于特定类型转移控制,但有时我们还需要对传入值进行额外检查。
例如,用 if
检查字符串长度:
static double getDoubleValueUsingIf(Object o) {
return switch (o) {
case String s -> {
if (s.length() > 0) {
yield Double.parseDouble(s);
} else {
yield 0d;
}
}
default -> 0d;
};
}
用带守卫的模式可以更优雅地解决:
static double getDoubleValueUsingGuardedPatterns(Object o) {
return switch (o) {
case String s when s.length() > 0 -> Double.parseDouble(s);
default -> 0d;
};
}
核心优势:带守卫的模式让我们避免在
switch
块内写额外if
,直接将条件逻辑移到case
标签。
4.3. 基本类型模式
Java SE 24 作为预览特性,支持在 switch
case
标签中使用基本类型模式。例如结合带守卫的模式:
void primitiveTypePatternExample() {
Random r=new Random();
switch (r.nextInt()) {
case 1 -> System.out.println("int is 1");
case int i when i > 1 && i < 100 -> System.out.println("int is greater than 1 and less than 100");
default -> System.out.println("int is greater or equal to 100");
}
}
支持范围:选择器表达式可以是任意基本类型(包括
long
、float
、double
、boolean
)及其包装类。
5. Switch 特殊场景
使用模式匹配时需注意几个关键场景。
5.1. 值覆盖检查
使用模式匹配时,Java 编译器会检查类型覆盖情况。
考虑一个只覆盖 String
类型的 switch
:
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case String s -> Double.parseDouble(s);
};
}
编译器会报错:
[ERROR] Failed to execute goal ... Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[10,16] the switch expression does not cover all possible input values
原因:**
case
标签必须覆盖所有可能的输入值(本例中是所有Object
类型)**。可用default
替代具体类型。
5.2. 子类顺序问题
使用子类模式时,**case
顺序至关重要**。
看一个 String
在 CharSequence
之后的错误示例:
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case CharSequence c -> Double.parseDouble(c.toString());
case String s -> Double.parseDouble(s);
default -> 0d;
};
}
编译错误:
[ERROR] Failed to execute goal ... Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[12,18] this case label is dominated by a preceding case label
问题本质:由于
String
是CharSequence
的子类,第二个case
永远不会执行——所有字符串对象会被第一个case
捕获。
5.3. 处理 null 值
早期 Java 中,向 switch
传 null
会抛 NullPointerException
。现在类型模式支持将 null
作为独立 case
处理:
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case String s -> Double.parseDouble(s);
case null -> 0d;
default -> 0d;
};
}
约束:
switch
表达式不能同时包含default
和总类型模式(覆盖所有输入值的类型)。否则编译报错:[ERROR] Failed to execute goal ... Compilation failure [ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[14,13] switch has both a total pattern and a default label
最后提醒:即使使用了模式匹配,
switch
仍可能抛NullPointerException
——但仅当缺少null
匹配的case
时。
6. 总结
本文深入探讨了 Java SE 21 引入的 switch
模式匹配特性。我们了解到:
- 通过在
case
标签中使用模式,选择逻辑从简单相等判断升级为模式匹配 - Java SE 24 进一步增加了基本类型模式支持
- 实际应用中需特别注意值覆盖、子类顺序和
null
处理三大场景
模式匹配让 switch
语句更灵活强大,是 Java 语言现代化的重要一步。