case表達式既能夠用字面值常量,也能夠用final修飾且初始化過的變量。例如如下代碼可正常編譯並執行:html
public static int test(int i) { final int j = 2; int result; switch (i) { case 0: result = 0; break; case j: result = 1; break; case 10: result = 4; break; default: result = -1; } return result; }
可是沒有初始化就不行,好比下面的代碼就沒法經過編譯java
public class SwitchTest { private final int caseJ; public int test(int i) { int result; switch (i) { case 0: result = 0; break; case caseJ: result = 1; break; case 10: result = 4; break; default: result = -1; } return result; } SwitchTest(int caseJ) { this.caseJ = caseJ; } public static void main(String[] args) { SwitchTest testJ = new SwitchTest(1); System.out.print(testJ.test(2)); } }
下面兩種幾乎同樣的代碼,會編譯出截然不同的字節碼。數組
public static int test(int i) { int result; switch (i) { case 0: result = 0; break; case 2: result = 1; break; case 10: result = 4; break; default: result = -1; } return result; }
對應字節碼安全
public static int test(int); Code: 0: iload_0 1: lookupswitch { // 3 0: 36 2: 41 10: 46 default: 51 } 36: iconst_0 37: istore_1 38: goto 53 41: iconst_1 42: istore_1 43: goto 53 46: iconst_4 47: istore_1 48: goto 53 51: iconst_m1 52: istore_1 53: iload_1 54: ireturn
public static int test(int i) { int result; switch (i) { case 0: result = 0; break; case 2: result = 1; break; case 4: result = 4; break; default: result = -1; } return result; }
public static int test(int); Code: 0: iload_0 1: tableswitch { // 0 to 4 0: 36 1: 51 2: 41 3: 51 4: 46 default: 51 } 36: iconst_0 37: istore_1 38: goto 53 41: iconst_1 42: istore_1 43: goto 53 46: iconst_4 47: istore_1 48: goto 53 51: iconst_m1 52: istore_1 53: iload_1 54: ireturn
兩種字節碼,最大的區別是執行了不一樣的指令:lookupswitch和tableswitch。dom
可是,在分支比較少的狀況下,O(log n)其實並不大。n=2時,log n 約爲2.8;即便n=100, log n 約爲 6.6,與1仍未達到1個數量級的差距。jvm
在JDK1.8環境下,經過檢索langtools
這個包,能夠在langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java看到如下代碼:ide
long table_space_cost = 4 + ((long) hi - lo + 1); // words long table_time_cost = 3; // comparisons long lookup_space_cost = 3 + 2 * (long) nlabels; long lookup_time_cost = nlabels; int opcode = nlabels > 0 && table_space_cost + 3 * table_time_cost <= lookup_space_cost + 3 * lookup_time_cost ? tableswitch : lookupswitch;
這段代碼的上下文:性能
能夠看出,決策的條件綜合考慮了時間複雜度(table_time_cost/lookup_time_cost)和空間複雜度(table_space_cost/lookup_space_cost),而且時間複雜度的權重是空間複雜度的3倍。學習
存疑點:優化
通常來講,更多的限制能帶來更好的性能。
從上文能夠看出,不管是tableswitch仍是lookupswitch,都有對隨機查找的優化,而if...else...是沒有的,能夠看下面的源碼和字節碼。
public static int test2(int i) { int result; if(i == 0) { result = 0; } else if(i == 1) { result = 1; } else if(i == 4) { result = 4; } else { result = -1; } return result; }
public static int test2(int); Code: 0: iload_0 1: ifne 9 4: iconst_0 5: istore_1 6: goto 31 9: iload_0 10: iconst_1 11: if_icmpne 19 14: iconst_1 15: istore_1 16: goto 31 19: iload_0 20: iconst_4 21: if_icmpne 29 24: iconst_4 25: istore_1 26: goto 31 29: iconst_m1 30: istore_1 31: iload_1 32: ireturn
舉例以下,這段源碼有兩個特色:
public static int testString(String str) { int result = -4; switch (str) { case "abc": result = 0; break; case "def": result = 1; break; case "ghi": break; case "test": case "test2": result = 1; break; default: result = -1; } return result; }
對應字節碼
public static int testString(java.lang.String); Code: 0: bipush -4 2: istore_1 3: aload_0 4: astore_2 5: iconst_m1 6: istore_3 7: aload_2 8: invokevirtual #2 // Method java/lang/String.hashCode:()I 11: lookupswitch { // 5 96354: 60 99333: 74 102312: 88 3556498: 102 110251488: 116 default: 127 } 60: aload_2 61: ldc #3 // String abc 63: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 66: ifeq 127 69: iconst_0 70: istore_3 71: goto 127 74: aload_2 75: ldc #5 // String def 77: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 80: ifeq 127 83: iconst_1 84: istore_3 85: goto 127 88: aload_2 89: ldc #6 // String ghi 91: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 94: ifeq 127 97: iconst_2 98: istore_3 99: goto 127 102: aload_2 103: ldc #7 // String test 105: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 108: ifeq 127 111: iconst_3 112: istore_3 113: goto 127 116: aload_2 117: ldc #8 // String test2 119: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 122: ifeq 127 125: iconst_4 126: istore_3 127: iload_3 128: tableswitch { // 0 to 4 0: 164 1: 169 2: 174 3: 177 4: 177 default: 182 } 164: iconst_0 165: istore_1 166: goto 184 169: iconst_1 170: istore_1 171: goto 184 174: goto 184 177: iconst_1 178: istore_1 179: goto 184 182: iconst_m1 183: istore_1 184: iload_1 185: ireturn
能夠看到與整型常量的不一樣:
爲何要再生成一段tableswitch?從字節碼來看,兩個平行的分支("test"和"test2"),雖然沒有在tableswitch中用同一個數組下標,可是使用了同一個跳轉行177,在這種狀況下減小了字節碼冗餘。
樣例代碼以下
public static int testEnum(StatusEnum statusEnum) { int result; switch (statusEnum) { case INIT: result = 0; break; case FINISH: result = 1; break; default: result = -1; } return result; }
對應字節碼
public static int testEnum(com.example.StatusEnum); Code: 0: getstatic #9 // Field com/example/SwitchTest$1.$SwitchMap$com$example$core$service$domain$enums$StatusEnum:[I 3: aload_0 4: invokevirtual #10 // Method com/example/core/service/domain/enums/StatusEnum.ordinal:()I 7: iaload 8: lookupswitch { // 2 1: 36 2: 41 default: 46 } 36: iconst_0 37: istore_1 38: goto 48 41: iconst_1 42: istore_1 43: goto 48 46: iconst_m1 47: istore_1 48: iload_1 49: ireturn
能夠看到,使用了枚舉的ordinal方法肯定序號。
經過查看字節碼,能夠發現源碼的break關鍵字,對應的是字節碼goto到具體行的語句。 若是不用break,那麼對應的字節碼就會「滑落」到下一行語句,繼續執行。
Mac下preference
->Tools
->External Tools
,點擊+
,按以下頁面配置便可。
Windows下須要將上圖填入的javap改成javap.exe。
注意:每次查看字節碼前,要確保對應類被從新編譯,才能看到最新版。
這種狀況的真實緣由是,JDK設置不一致,IDE沒有徹底使用預期的編譯器版本。
在IDEA裏能夠這樣解決:
Project Settings
-> Project
設置項目語言
若是仍未解決,檢查
File
-> Project Structure
-> Modules
, 查看全部模塊是否都是預期的等級。
還有一處也能夠看下File
-> Settings
-> Compiler
-> Java Compiler
. 這裏能夠設置項目及模塊的編譯器版本。
文中全部log n均爲以2爲底n的對數。
本文的寫做契機是參加公司的XX安全學習,提到了switch...case...和if...else...的性能有差別,所以花了一天研究了一番。
經過字節碼分析java中的switch語句
Difference between JVM's LookupSwitch and TableSwitch?
IntelliJ switch statement using Strings error: use -source 7
Intellij idea快速查看Java類字節碼