我們知道自java 1.7以後, java switch開始支持String類型。那有沒有同學思考過,java是如何支持String類型的?
我們看下面這段代碼:
public class SwitchString {
public static void main(String[] args) {
switch (args[0]) {
case "A" : break;
case "B" : break;
default :
}//switch
}
}
在JDK1.8.0_152的環境下,我們使用javac編譯SwicthString.java這個源文件。得到了SwitchString.class這個字節碼文件。而後我們利用javap反編譯SwitchString.class文件。得到下圖的結果(我直接截取的main方法的反編譯結果):
圖中,我用藍色線,圈起來的部分,出現了,65, 66字樣,而在上面又出現了,String的hashCode方法。我們知道很多高級語言的,像C,C++都是自switch誕生以來,便支持int類型的,java也不例外。String的hashCode方法返回的便是其int成員變量hash。而65則是字符'A'的ASCII碼,66是字符'B'的ASCII碼。同時65也是String類型"A"的hash值,66也是String類型的"B"的hash值。
我們是不是可以猜測,JVM就是用String的hash值這一特性,來使switch支持String類型的。
大膽假設,小心求證。我們藉助IDEA反編譯SwitchString.class字節碼文件。得到下面這份直觀的反編譯結果。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class SwitchString {
public SwitchString() {
}
public static void main(String[] var0) {
String var1 = var0[0];
byte var2 = -1;
switch(var1.hashCode()) {
case 65:
if (var1.equals("A")) {
var2 = 0;
}
break;
case 66:
if (var1.equals("B")) {
var2 = 1;
}
}
switch(var2) {
case 0:
case 1:
default:
}
}
}
通過這份直觀的結果,我們知道了jvm是先調用String的hashCode方法得到hash值,然後將case中的常量換掉。但是有個很奇怪的地方,爲什麼在case中,還要再使用String的equals方法呢?這就和String的hashCode計算hash值有關了。我附上JDK1.8.0_152中關於String的hashCode計算方法:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
讀者如果閱讀過String的源碼,就會知道這樣,String的Hash可能會衝突,即兩個不同的String可能計算出相同的hash值。
也因此JVM纔會又用String的equals方法再次比較,並且在下面又增加了一個Switch-byte結構。
【總結】:java中switch支持String,是利用String的hash值,本質上是switch-int結構。並且利用到了equals方法來防止hash衝突的問題。最後利用switch-byte結構,精確匹配。
【思考】:string的hash衝突,在其他數據結構中,如HashMap<String, T>, HashSet<String>中是如何解決的?兩個switch結構,下面那個一定會是switch-byte結構嗎?