就看第一句我圈起来的话。后面的描述都是围绕着这句话在展开描述。
Java 虚拟机的 tableswitch 和 lookupswitch 指令,只支持 int 类型。
好,那我现在来问你:switch 语句的表达式可以是哪些类型的值?注意我说的是表达式。
这个答案在《Java 语言规范》里面也写着的:
你看,8 种基本类型已经支持了char、byte、short、int 这4 种,而这 4 种都是可以转化为 int 类型的。
而剩下的 4 种:double、float、long、boolean 不支持。
为什么?
你就想,你就结合我前面讲的内容,把你的小脑壳子动起来,为什么这 4 种不支持?
因为 double、float 都是浮点类型的,tableswitch 和 lookupswitch 指令操作不了。
因为 long 类型 64 位了,而tableswitch 和 lookupswitch 指令只能操作 32 位的 int 。
这两个指令对于 long 是搞不动的。
而至于 boolean 类型,还需要我说嘛?
你拿着 boolean 类型放到 switch 表达式里面去,你不觉得害臊吗?
你就不能写个 if(boolean) 啥的?
然后你又发动你的小脑壳子想:对于 Character、Byte、Short、Integer 这 4 个包装类型是怎么支持的呢?
上个图,左上是 java 文件,右上是 jad 文件,下面是字节码:
拆了个箱,实际还是用的 int 类型,这个不需要我细讲了吧?
于是你接着想对于 String 类型是怎么支持的呢?
它会先转 hashCode。hashCode 肯定是稀疏的,所以用 lookupswitch。
然后在用 var3 这个变量去做一次 switch,经过转化后 var3 一定不是稀疏的,所以用 tableswitch:
你再多想一步,因为是用的 String 类型的 hashcode,那如果出现了哈希冲突怎么办?
看一下这个例子:
冲突了就再配一个 if-else 。
不用多说了吧。
最后,你再想,这个枚举又是怎么支持的呢?
比如下面这个例子,看字节码,只看到了使用了 tableswitch:
它们分别长这样的:
上面的 SwitchEnumTest.class 文件看不出来什么道道。
但是下面的 SwitchEnumTest$1.class 文件里面还是有点东西的。
可以看到静态代码块里面有个数组,数组里面的参数是枚举的类型,然后调用了枚举的 ordinal 方法。这个方法的返回值是枚举的下标位置。
在 class 文件里面获取的信息有限,需要祭出 jad 文件来瞅一眼来:
上面就是 java 文件对应的 jad 文件。
标号为 ① 的地方是我们传入的 switch 里面的表达式,线程状态枚举中的 RUNNABLE。
标号为 ② 的地方是给 int 数值中的位置赋值为 2。那么是哪个位置呢?
RUNNABLE 在线程状态枚举中的下标位置,如下所示,下标位置是1:
编号为 ③ 的地方是把 int 数值中下标为 1 的元素取出来?
我们前面刚刚放进去的。取出来是 2。
于是走到编号为 ④ 的逻辑中去。执行最终的输出语句。
所以写到这里,我想我更加能明白著名程序员沃·滋基索德的一句话:
相对于 String 类型而言,枚举简直天生就支持 Switch 操作。
奇怪的知识点
再送给你一个我在写这篇文章的时候学到的一个奇怪的知识点。
我们知道 switch 的表达式和 case 里面都是不支持 null 的。
你有没有想过一个问题。case 里面为什么不支持 null?如果表达式为 null ,我们就拿着 null 去 case 里面匹配,这样理论上做也是可以做的。
好吧,应该也没有人想这个问题。当然,除了一些奇奇怪怪的面试官。
这个问题我在《Java 语言规范》里面找到了答案:
the designers of the Java programming language。
我的妈呀,这是啥啊。
Java 编程语言设计者,这是赏饭吃的祖师爷啊!
《Java 语言规范》里面说:根据 Java 编程语言设计者的判断,抛出空指针这样做比静默地跳过整个 switch 语句或选择在 default 标签(如果有)里面继续执行语句要好。
别问,问就是祖师爷觉得这样写就是好的。
一个基本上用不到的知识点送给大家,不必客气: