概述
switch是在java开发中一个很常用的关键字,一般用于对于一个变量等于不同值时候做不同的处理,比起直接用if、else if、else if、else
这样会显得简洁一些,也更加符合人最直观的理解,加强代码的可读性。
传统用法
jdk7之前,switch只有1种用法,switch仅支持整形和枚举。实际上,枚举也是通过ordinal()
方法转换成了int。所以,可以简单理解成仅支持整形。
穿透
相比于直接用if、else if、else if、else
这样的写法,switch有个比较大的特点是穿透,即一旦符合条件进入了case,如果没有显示break,就会一直进入下面的case,例如我们要写个函数求月份归属的季节,代码如下:
private static Season getSeasonByMonth(int month) {
Season season = null;
// 这里简单认为春季是2、3、4月,夏季是5、6、7月,秋季是8、9、10月,冬季是11、12、1月
// 注意这里month的取值在0-11之间,是日常理解的月份-1
switch (month) {
case 1:
case 2:
case 3:
season = Season.SPRING;
break;
case 4:
case 5:
case 6:
season = Season.SUMMER;
break;
case 7:
case 8:
case 9:
season = Season.AUTUMN;
break;
case 10:
case 11:
case 0:
season = Season.WINTER;
break;
default:
System.out.println("month值不合法,未能找到归属的季节");
}
return season;
}
这里就利用了穿透的特性,避免了代码重复。不过这种特性也带来了额外的成本,就是有点反直觉,天然认为不符合case条件就不会进入,忘记写break而导致代码bug是初级程序员使用switch常见的错误。
另外,使用switch时候,最后写上default是一个好习惯,作用跟else是类似的,可以确保这个switch语句逻辑完整,无论什么值,都会走到一种情况,即使在写的时候,觉得这个default永远也不会进去,但是程序异常往往会发生在这些意想不到的地方。
匹配String
从jdk1.7开始,switch支持String,不过这里其实只是新增的一个语法糖,switch String实际上是利用了String自身的hashCode()
方法转换成了int,然后通过equles来保护哈希冲突的情况,实际上JVM层面依然还是只支持整形,跟switch 枚举有些类似。
jdk14的新用法
需要说明,以下的一些特性在jdk12、jdk13就已经开始预览,归于jdk14是因为在jdk14中才成为正式的特性。
yield返回值
switch语句之前仅仅是在某些情况下替代if,这里可以使用yield来设置返回值,switch就正式变成了表达式,上面的方法可以写成:
Season season = switch (month) {
case 1:
case 2:
case 3:
yield Season.SPRING;
case 4:
case 5:
case 6:
yield Season.SUMMER;
case 7:
case 8:
case 9:
yield Season.AUTUMN;
case 10:
case 11:
case 0:
yield Season.WINTER;
default:
System.out.println("month值不合法,未能找到归属的季节");
yield Season.SPRING;
};
case多值 和 箭头表达值
之前,我们想在switch等于某些值的时候执行相同的逻辑,我们会使用switch穿透的特性,但是这个特性是双刃剑,也比较容易出错,这里支持case多值之后,就有了更加简洁直观的写法,符合人的直觉,例如下面的方法可以改写成:
private Season getSeasonByMonth(int month) {
Season season = null;
// 这里简单认为春季是2、3、4月,夏季是5、6、7月,秋季是8、9、10月,冬季是11、12、1月
// 注意这里month的取值在0-11之间,是日常理解的月份-1
switch (month) {
case 1, 2, 3 -> season = Season.SPRING;
case 4, 5, 6 -> season = Season.SUMMER;
case 7, 8, 9 -> season = Season.AUTUMN;
case 10, 11, 0 -> season = Season.WINTER;
default -> System.out.println("month值不合法,未能找到归属的季节");
}
return season;
}
jdk17的新用法
模式匹配
这里的模式匹配是指,可以通过传入对象的不同类型,来走不同的逻辑,而不是仅仅是不同的值,例如:
String s1 = switch (o) {
case Integer ii -> "o is Integer";
case String ss -> "o is String, length = " + s.length();
default -> "unknown";
};
注意,这里匹配了类型之后,可以用一个变量接收,这样可以直接使用类型自带的方法了,不然还需要对o进行一次强转,这样会更加方便。
保护模式
case条件不仅仅是一个值,还可以增加表达式进行综合判断,前面说过的支持switch String的语法糖就适合这种保护模式,因为还需要equles来进行一次保护校验
String s1 = switch (o) {
case Integer ignored -> "o is Integer";
case String ignored && "123".equals(o) -> "o is String";
default -> "unknown";
};
空值
之前switch是无法接受空值的,传空就会抛出空指针异常,这里支持空值后,不需要在switch之前判空,空也可以作为一种值,跟其他的值一起并列:
switch (s) {
case "123":
System.out.println("s is 123");
break;
case null:
System.out.println("s is null");
break;
}
总结
switch在java开发中是个历史悠久的关键字,也比较常用。传统用法时,注意穿透现象,以及写上default让逻辑完备。在更新版本的jdk中,可以使用case多值来代替之前的穿透,用yield或者箭头来做值返回。特定情况下模式匹配、保护模式、空值等新用法也会让代码更加简洁和易于理解。
说句题外话,既然新版本这么香,就不要一直守着java8不放了对不对,毕竟,不断尝试新的东西,也是一个程序猿基本的素养。