1.Switch表达式
Switch 语句,这是 Java 入门中控制流程的部分,它是除 if 语句外的另外一种条件判断,提供了比连续的 if-else 语句更加清晰和结构化的选择机制。
Switch 语句依然存在一些显著的缺陷:
- "Fall-through" 行为:在没有显式
break
语句的情况下,Switch 语句会从一个 case "穿透" 到下一个 case,忽略了这个会导致不可饶恕的错误。 - 代码冗余:每个 case,我们都需要重复类似的代码结构,增加了代码的冗余和维护难度。
为了解决 Switch 的现存问题,Java 12 引入全新的Switch 表达式,该表达式不仅增强了 Switch 语句的功能,还大幅提高了其灵活性和表达能力。
-
传统Switch
public static String getTypeOfDay(String day) {
String typeOfDay;
switch (day) {
case "MONDAY":
case "TUESDAY":
case "WEDNESDAY":
case "THURSDAY":
case "FRIDAY":
typeOfDay = "Weekday";
break;
case "SATURDAY":
case "SUNDAY":
typeOfDay = "Weekend";
break;
default:
typeOfDay = "Unknown";
}
return typeOfDay;
}
-
Switch表达式
新的 Switch 表达式引入了
->
操作符,用于替代传统的冒号(:
)。与传统的 Switch 语句不同,使用->
的 case 分支不会出现"fall-through"
现象,因此不需要 break 语句来防止穿透。这减少了代码的复杂性,也降低了编程错误的风险。
public static String getTypeOfDay(String day) {
String typeOfDay = switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
case "SATURDAY", "SUNDAY" -> "Weekend";
default -> "Unknown";
};
return typeOfDay;
}
- 返回值:传统的 Switch 仅仅只是语句,用来控制流程的,无法返回值。但是新的 Switch 可以作为表达式使用,支持返回值。
->
代替:老的 Switch 需要在每个案例后面使用 break ,否则会发生“穿透”,而新的不需要,它会自动终止。- 多值匹配:传统的 Switch 无法在一个案例标签中匹配多个值,而新的 Switch 表达式允许一个
case
匹配多个值,用","
分割即可。 - 更简洁的语法: 整体代码更简洁,易于阅读和维护。
-
Switch表达式扩展
为了进一步扩展 Switch 的功能,Java 13 引入yield
关键字来处理多分支结构中的返回值。
yield
用于在Switch表达式的每个分支中返回一个值,这点与传统的Switch语句需要通过变量赋值来返回值不同。同时在使用->
的情况下,如果分支逻辑比较复杂,需要多条语句来处理,那么可以在这些语句的最后使用yield
来返回最终的结果。
在 Java 12 中,虽然他允许直接从Switch表达式中返回值,但在处理复杂的逻辑时,仍需依赖外部变量来返回结果。比如下面这段简单的逻辑:
public static String getTypeOfDay(String day) {
String typeOfDay = switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
case "SATURDAY", "SUNDAY" -> "Weekend";
default -> "Unknown";
};
return typeOfDay;
}
对于这简单的逻辑 ,Java 12 可以直接返回,但是这里想在 default 处增加一个判断 day 是否为空的逻辑,这个时候就无法简单使用 ->
直接返回了,需要定义一个外部变量来处理
public static String getTypeOfDay(String day) {
String typeOfDay = null;
switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> typeOfDay = "Weekday";
case "SATURDAY", "SUNDAY" -> typeOfDay = "Weekend";
default -> {
// 处理复杂逻辑
if (day.isEmpty()) {
typeOfDay = "day is empty";
} else {
typeOfDay = "Unknown";
}
}
};
return typeOfDay;
}
但是,在 Java 13 中,我们可以使用 yield 在Switch表达式中直接返回:
public static String getTypeOfDay(String day) {
return switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
case "SATURDAY", "SUNDAY" -> "Weekend";
default -> {
if (day.isEmpty()) {
yield "day is empty";
} else {
yield "Unknown";
}
}
};
}
在Switch表达式中处理复杂的case逻辑时,yield
使得在每个分支内部可以执行多条语句,并返回值,从而简化了代码结构并提高了可读性。
但是,Switch 表达式只有一种类型,如果要处理多种类型呢?只能这样处理 :
Object obj = ... // 某个对象
switch (obj.getClass().getName()) {
case "java.lang.String":
String str = (String) obj;
// 处理字符串
break;
case "java.lang.Integer":
Integer i = (Integer) obj;
// 处理整数
break;
case "com.lang.Long":
Long lon = (Long) obj;
// 处理MyClass实例
break;
// 其他类型
}
2.模式匹配
instanceof
用于检查一个对象是否是特定类的实例或者该类的子类的实例。它通常用在条件语句中,以确定对象的类型,从而避免在向下转型时发生 ClassCastException
。在 Java 14 前,我们一般都是这样写
if (object instanceof String) {
// 返回 true,确认是 String 类型,强制转换为 String 类型后使用
String str = (String) object;
}
if 语句里面的强制转换显得很是多余,所以 Java 14 引入模式匹配的 instanceof
来解决这个问题,它允许在 instanceof
操作符的条件判断中直接定义一个变量,如果对象是指定的类型,这个变量会被初始化为被检查的对象,可以立即使用,无需额外的类型转换,如:
if (obj instanceof String str) {
// 可以直接使用 str,而不需要显式的类型转换
}
3.Switch模式匹配
用于 instanceof
的模式匹配在Java 16 成为正式特性,到了 Java 21 模式匹配的应用扩展到了switch表达式并成为正式特性,这标志着 Switch 表达式又得到了一次增强。
在 Java 21 中,switch 表达式允许使用模式匹配来处理对象类型,这样就可以直接在 switch 语句中检查和转换类型,而不需要额外的 if...else
结构和显式类型转换。
-
类型模式
这是一种比较常见的模式,它允许在 switch 语句的 case 分支中直接匹配对象的类型。例如,case String s允许你在该分支中直接作为字符串类型的 s 来使用,避免了显式的类型检查和强制类型转换。举个例子来说明下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "jjjava" };
for (Object obj: objects) {
if (obj instanceof Integer intR) {
System.out.println("为整数型:" + intR);
} else if (obj instanceof Float floatR) {
System.out.println("为浮点型:" + floatR);
} else if (obj instanceof Double doubleR) {
System.out.println("为双精度浮点数:" + doubleR);
} else if (obj instanceof String str) {
System.out.println("为字符串:" + str);
} else {
System.out.println("其他类型:" + obj);
}
}
}
用 Switch
表达式来改造下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "jjjava" };
for (Object obj: objects) {
switch (obj) {
case Integer intR -> System.out.println("为整数型:" + intR);
case Float floatR -> System.out.println("为浮点型:" + floatR);
case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
case String str -> System.out.println("为字符串:" + str);
default -> System.out.println("其他类型:" + obj);
}
}
}
相比上面的 if...else
简洁了很多。同时在 Java 21之前,Switch 选择器表达式只支持特定类型,即基本整型数据类型byte
、short
、char
和int
;对应的装箱形式Byte
、Short
、Character
和Integer
;String
类;枚举类型。现在有了类型模式,Switch 表达式可以是任何类型啦。
-
空模式
在Java 21之前,向switch
语句传递一个null
值,会抛出一个NullPointerException
,现在可以通过类型模式,将 null 检查作为一个单独的case标签来处理,如下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "jjjava" };
for (Object obj: objects) {
switch (obj) {
// 省略...
case null -> System.out.println("为空值");
default -> System.out.println("其他类型:" + obj);
}
}
}
case null
可以直接匹配值为 null
的情况。
-
守卫模式
守卫模式允许我们在 case 标签后添加一个额外的条件。只有当类型匹配并且额外条件为真时,才会进入该 case 块。
比如上面例子,我们要将字符串那块逻辑调整下,比如长度大于 5 的为长字符串,小于 5 的为短字符串,在不使用守卫模式的情况下,我们一般这样写:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "jjjava" };
for (Object obj: objects) {
switch (obj) {
case Integer intR -> System.out.println("为整数型:" + intR);
case Float floatR -> System.out.println("为浮点型:" + floatR);
case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
case String str -> {
if (str.length() > 5) {
System.out.println("为长字符串:" + str);
} else {
System.out.println("为短字符串:" + str);
}
}
case null -> System.out.println("为空值");
default -> System.out.println("其他类型:" + obj);
}
}
}
这种写法就显得不是那么友好,使用守卫模式如下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "jjjava" };
for (Object obj: objects) {
switch (obj) {
case Integer intR -> System.out.println("为整数型:" + intR);
case Float floatR -> System.out.println("为浮点型:" + floatR);
case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
case String str && str.length() > 5 -> System.out.println("为长字符串:" + str);
case String str -> System.out.println("为短字符串:" + str);
case null -> System.out.println("为空值");
default -> System.out.println("其他类型:" + obj);
}
}
}
使用守卫模式,我们可以编写更灵活和表达性强的代码。