1、在 Java 中,if 和 switch 哪一个执行效率更高?
结论:switch 平均更快
public class Animal {
}
class Dog extends Animal{
}
class Cat extends Animal{
}
class Pig extends Animal{
}
public static Animal test1(int n) {
if (n == 1) {
return new Dog();
} else if (n == 2) {
return new Cat();
} else if (n == 3) {
return new Pig();
} else {
return null;
}
}
public static Animal test2(int n) {
return switch (n) {
case 1 -> new Dog();
case 2 -> new Cat();
case 3 -> new Pig();
default -> null;
};
}
说明:
1、对于 if:在 Java 的字节码层面,if 条件语句和我们编写的代码是类似的,也是按照 if(?),if(?) 的方式进行判断的,
若最后一个 if 条件才满足就会判断所有的 if 条件判断语句,依次向下执行。
2、对于 switch:在 Java 底层,对于 switch 语句会维护一个类似 HashMap 的数据结构名字叫做 tableswitch ;首先会有上界(high)和下界(low)。
例如上面的代码中 (high,low) 就是 (3,1) 。HashMap 中 (key,value) 存储的数据是 (n的值,指向执行的代码) 。
所以可以知道,对于 switch 语句,当给定的值不在上界和下界的区间时就会直接返回,当在区间类,也可以根据 HashMap 数据结构马上找到需要执行的代码。这样就比 if 语句快很多了。(其中28,36,44表示字节码对应的行号)
2、 底层 switch 使用了几种表结构?
在使用 switch 事,底层有 tableswitch 和 lookupswitch 两种表结构。
两种表结构大致是相同的,当 switch 语句中 case 中的值相对集中时就会采用 tableswitch 结构。如下,tableswitch表格大小情况:
case值连续
当 case 的值为 1,2,3 是会将 索引为 0,1,2 的 value 指向执行代码,也会将其指向 null。
case值不连续
当 case 的值为 1,2,5 是会将 索引为 0,1,4 的 value 指向执行代码,当 n 的值为 3,4 时,对应的索引就是 2,3 。也会将其指向 null。
当 case 中的数据不集中时,例如下代码:case 中的数据间隙很大。就会使用 lookupswitch 结构
public static Animal test3(int n) {
return switch (n) {
case 100 -> new Dog();
case 300 -> new Pig();
case 200 -> new Cat();
default -> null;
};
}
对应的 lookupswitch 表结构如下:会根据 case 中的数据进行排序,然后然后为每一个数据指定一个从 0 到 n 的索引,相应的索引对应指定的具体数据,进一步指向相关的执行代码。
当执行代码时,当接收到数据 n 时,会将其转化根据当前的相关规则生成指定的索引。进一步找到真实需要执行的代码【这部分和 tableswitch 类似】。
3、switch 可以匹配的数据类型?
从 JDK1.0 开始 switch 就可以匹配 int 、byte、short 和 char 四种数据类型。
从 JDK1.5 开始,引入了相关的包装类 Integer、Byte、Short 和 Character 四种。
从 JDK 1.7 开始,引入了 String 和 Enum 类型。
目前 JDK 中的 switch 一种支持这 10 中数据类型。
int 、byte、short 和 char;Integer、Byte、Short 和 Character 这 8 中就是通过tableswitch 和 lookupswitch 进行匹配的,那么 String 和 枚举是如何进行匹配的呢?
4、switch 是如何匹配枚举类型的?
定义一个枚举:
public enum Day {
MON, TUE, WED, THU, FIR, SAT, SUN;
}
例如下列代码
public static String test4(Day day) {
return switch (day) {
case MON, TUE, WED, THU, FIR -> "工作日";
case SAT, SUN -> "休息日";
default -> "";
};
}
如下图所示,在匹配枚举类型时,底层维护了两个表格。对于枚举类型,每一个枚举变量都有一个相应的序号,从 0 开始。使用 ordinal() 方法就可以获取相关的序号。
第一张表格就是 Java 维护的数组,索引部分就是枚举的序号,值的部分就是连续的整数。
第二张表就是一张 tableswitch,它会根据第一张表格的值生成一个下界。tableswitch 表格里面的值就对应相应的代码行。
执行流程是:传递一个枚举对象,获取相关的序号,根据序号找到值,根据值减去下界就是tableswitch 表格中的索引,然后再根据索引执行相关的代码。
5、switch 在底层是如何匹配字符串类型的?
如下代码
public static String test5(String s) {
return switch (s) {
case "1", "2", "3", "4", "5" -> "工作日";
case "6", "7" -> "休息日";
default -> "不合法";
};
}
switch 匹配 String 的执行流程
- 计算所有 case 里面的字符串的 hashCode,获取下界。
- 建立一个 tableswitch 表格,索引部分就是每一个 hashCode - 下界,值的部分指向了一个 if 条件判断语句。在 if 条件判断部分有一个变量 t ,初始值为 -1,后面会根据这个 t 的值去第二个 tableswitch 表格中进一步查找。
- 在比较第一个表格时,当得出的索引为 3 时,会进一步 if 判断当前的 s 的值是不是 "4",匹配成功地情况下才会将变量 t 进行赋值,进一步去第二个 tableswitch 中查询。
思考:
每一个字符串都有一个唯一的 hashCode,没什么要通过 if 维护两个 tableswitch 表格呢?
解答:因为不同的字符串的 hashCode 值可能会相同。