Java switch詳解
在Java中switch的形式:
switch(key)
{
case num1:
//可以加入自己的業務邏輯
break;
case num2:
//可以加入自己的業務邏輯
break;
default:
//可以加入自己的業務邏輯
break;
}
上述程式碼中switch(key)中的key可以是什麼型別?
(1)最早時,只支援int、char、byte、short這樣的整型的基本型別或對應的包裝型別Integer、Character、Byte、Short常量
(3)1.7開始支援String,但不允許為null,原理是藉助 hashcode( ) 來實現。
(4)此篇文章重點講述 tableswitch 和 lookupswitch
一、首先看下述程式碼
package com.Ycb.jvm; public class SwitchTest { /** * 程式碼片段一 * * @param i */ public void switch1(int i) { int j; switch (i) { case 1: j = 1; break; case 2: j = 2; break; case 5: j = 3; break; default: break; } } /** * 程式碼片段二 * * @param i */ public void switch2(int i) { int j; switch (i) { case 1: j = 1; break; case 5: j = 2; break; case 10000: j = 3; break; default: break; } } }
上述程式碼的邏輯幾乎一模一樣。主要就是case 中 的值不太一樣,方法switch1()中幾乎是連續的,方法switch2()中的跨度比較大,不連續。我們可以看下生成的位元組碼
方法switch1()對應的位元組碼(有省略):
public void switch1(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=1, locals=3, args_size=2 0: iload_1 1: tableswitch { // 1 to 5 1: 36 2: 41 3: 51 4: 51 5: 46 default: 51 } 36: iconst_1 37: istore_2 38: goto 51 41: iconst_2 42: istore_2 43: goto 51 46: iconst_3 47: istore_2 48: goto 51 51: return
方法switch2()對應的位元組碼(有省略):
public void switch2(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=2
0: iload_1
1: lookupswitch { // 3
1: 36
5: 41
10000: 46
default: 51
}
36: iconst_1
37: istore_2
38: goto 51
41: iconst_2
42: istore_2
43: goto 51
46: iconst_3
47: istore_2
48: goto 51
51: return
從上面的位元組碼中可以看出方法switch1()是使用tableswitch來輔助實現switch,如下
1: tableswitch { // 1 to 5
1: 36
2: 41
3: 51
4: 51
5: 46
default: 51
}
(1)對於tableswitch指令,這裡high為5,low為1,表中共有high-low+1個分支項,當jvm遇到tableswitch指令時,它會檢測switch(key)中的key值是否在low~high之間,如果不是,直接執行default部分,如果在這個範圍之內,它使用key-low這個項指定的地點跳轉。可見,tableswitch的效率是非常高的。感覺這個有點類似於陣列,我們知道陣列根據下標去查詢元素的時間複雜度是O(1),效率是非常高的。
(2)我們看下方法switch1()的原始碼,case 分支只有值 1 、2 和 5,並沒有 3 和 4。但是為了便於利用陣列的連續的特性,添加了 3 和 4,所以這也就解釋了,當case中的值不連續時不宜使用tableswitch的原因,會導致很多的空間浪費,比如方法switch2()中的case 分支 的值有1 、5 和 10000,如果繼續使用tableswitch,就需要補充上 2、3、4 和 6 ~9999中間的值,而這些其實很多都是無效的值,會導致浪費很多的空間。所以針對 方法 switch2()使用lookupswitch
方法switch2()是使用lookupswitch,如下
1: lookupswitch { // 3
1: 36
5: 41
10000: 46
default: 51
}
switch是控制選擇的一種方式,編譯器生成程式碼時可以對這種結構進行特定的優化,從而產生效率比較高的程式碼。在java中,編譯器根據分支的情況,分別產生tableswitch和lookupswitch兩中情況,其中tableswitch適用於分支比較集中的情況,而lookupswitch適用與分支比較稀疏的情況。不過怎麼算稀疏,怎麼算集中就是編譯器的決策問題了。
JVM規範中有如下一段內容:
Compilation ofswitch
statements uses the tableswitch and lookupswitch instructions. The tableswitch instruction is used when the cases of theswitch
can be efficiently represented as indices into a table of target offsets. Thedefault
target of theswitch
is used if the value of the expression of theswitch
falls outside the range of valid indices.Where the cases of theswitch
are sparse, the table representation of the tableswitch instruction becomes inefficient in terms of space. The lookupswitch instruction may be used instead.
The Java virtual machine specifies that the table of the lookupswitch instruction must be sorted by key so that implementations may use searches more efficient than a linear scan. Even so, the lookupswitch instruction must search its keys for a match rather than simply perform a bounds check and index into a table like tableswitch. Thus, a tableswitch instruction is probably more efficient than a lookupswitch where space considerations permit a choice.
if....else 與 switch....case的初步對比分析
我們把方法switch1()的程式碼改寫下,改為使用if....else實現,如下:
public void ifSwitch(int i) {
int j;
if (i == 1) {
j = 1;
} else if (i == 2) {
j = 2;
} else if (i == 5) {
j = 3;
}
}
我們檢視下位元組碼:
public void ifSwitch(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: iload_1
1: iconst_1
2: if_icmpne 10
5: iconst_1
6: istore_2
7: goto 27
10: iload_1
11: iconst_2
12: if_icmpne 20
15: iconst_2
16: istore_2
17: goto 27
20: iload_1
21: iconst_5
22: if_icmpne 27
25: iconst_3
26: istore_2
27: return
我們可以看到 if....else 是使用if_icmpne指令和goto來配合實現的。當傳入的值在if分支中比較靠後或者傳入的值壓根不在if分支中,就需要執行前面的很多條if_icmpne比較,而使用switch....case 的話,如果使用 tableswitch 那直接執行O(1)的時間複雜度就可以直接執行goto跳轉,如果採用的lookupswitch,時間複雜度也小很多。if ..else走邏輯判斷時,每條if語句都獨立需要載入,都要走一遍判斷。這就是耗時的機制問題了。switch..case 根據一個值進行多路分支,只做一次計算,然後將表示式的值與每個case的值比較,進而選擇哪一個case語句塊。
但是當if 中的 條件是個範圍,比如if(key>1 && key<2)這種形式,此時switch就無能為力了,switch只能使用在等值的場景。