啤酒免費代理0元鋪貨優(yōu)化軟件有哪些
揭秘Java switch語句中的case穿透現(xiàn)象
- 1. switch 語句簡介
- 2. case穿透現(xiàn)象的原因
- 關(guān)于 goto
- 3. switch和if的區(qū)別
- 4. 總結(jié)
導(dǎo)語:在 Java 開發(fā)中,我們經(jīng)常使用switch
語句來進(jìn)行條件判斷和分支選擇。然而,有一個令人困惑的現(xiàn)象就是,當(dāng)某個case
語句沒有加上break
關(guān)鍵字時,程序會繼續(xù)執(zhí)行下一個case
語句,這被稱為case穿透現(xiàn)象
。本文將揭秘case穿透現(xiàn)象
的原因,并解釋為何會出現(xiàn)這種行為。
1. switch 語句簡介
在開始揭秘case穿透現(xiàn)象
之前,我們先簡單回顧一下switch
語句的基本用法。switch
語句用于根據(jù)變量的不同取值執(zhí)行相應(yīng)的代碼塊。其語法結(jié)構(gòu)如下:
switch (expression) {case value1:// 執(zhí)行代碼塊1break;case value2:// 執(zhí)行代碼塊2break;...default:// 默認(rèn)代碼塊
}
switch case支持的6種數(shù)據(jù)類型:switch 表達(dá)式后面的數(shù)據(jù)類型只支持byte、short、int整形類型、字符類型char、枚舉類型和java.lang.String類型。
根據(jù)expression
的值,程序會跳轉(zhuǎn)到對應(yīng)的case
語句進(jìn)行匹配并執(zhí)行相應(yīng)的代碼塊,直到遇到break
關(guān)鍵字或者到達(dá)switch
語句的結(jié)尾。
如果某個case
語句沒有break
,程序會繼續(xù)執(zhí)行下一個case語句,這就是case穿透現(xiàn)象
。
我們看下面這個例子。
public class Test {public static void main(String[] args) {int i = 0;switch (i) {case 0:System.out.println("0");case 1:System.out.println("1");case 2:System.out.println("2");}}
}
打印結(jié)果:
0
1
2
2. case穿透現(xiàn)象的原因
按照慣用套路,看看字節(jié)碼能不能給個答案。
javac編譯
和javap查看
:
> javap -c -l Test.class
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: tableswitch { // 0 to 20: 281: 362: 44default: 52}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String 033: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;39: ldc #5 // String 141: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V44: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;47: ldc #6 // String 249: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V52: returnLineNumberTable:line 10: 0line 11: 2line 13: 28line 15: 36line 17: 44line 19: 52
}
根據(jù)提供的字節(jié)碼,我們來解釋一下case穿透
的情況。
在main方法中,通過tableswitch
指令實現(xiàn)了一個switch
語句。switch
語句會根據(jù)值進(jìn)行跳轉(zhuǎn),并執(zhí)行對應(yīng)的代碼塊。
在這個例子中,根據(jù)tableswitch
指令的參數(shù) {0 to 2}
,case
的范圍是從0到2。
- 當(dāng)
switch
的表達(dá)式的值為0時,程序會跳轉(zhuǎn)到標(biāo)簽為28的位置,然后繼續(xù)執(zhí)行28標(biāo)簽處的代碼塊。 - 為1時跳轉(zhuǎn)到標(biāo)號36代碼處;
- 為2時跳轉(zhuǎn)到標(biāo)號44代碼處;
- default則跳轉(zhuǎn)到標(biāo)號52代碼處。
這不,答案就出來了,當(dāng)case 0
匹配了之后,直接跳轉(zhuǎn)到標(biāo)號28代碼處開始執(zhí)行,輸出0,然后策馬奔騰,一路下坡,順序執(zhí)行完后面所有代碼,直到標(biāo)號52 return,方法完執(zhí)行完成,程序結(jié)束。
如果按照正常的思維,是不是case 0
匹配之后,跳到28,執(zhí)行完28、31、32輸出0之后,就應(yīng)該直接跳走,直接執(zhí)行49。
那么,這個【跳走】用字節(jié)碼應(yīng)該怎么表示?
關(guān)于 goto
再寫代碼樣例,這次我們在代碼中給每個case
都加上break
。
public class Test {public static void main(String[] args) {int i = 0;switch (i) {case 0:System.out.println("0");break;case 1:System.out.println("1");break;case 2:System.out.println("2");break;}System.out.println("Hello World");}
}
打印結(jié)果:
0
Hello World
重新編譯,再來看看字節(jié)碼。
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: tableswitch { // 0 to 20: 281: 392: 50default: 58}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String 033: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: goto 5839: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;42: ldc #5 // String 144: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V47: goto 5850: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;53: ldc #6 // String 255: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V58: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;61: ldc #7 // String Hello World63: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V66: returnLineNumberTable:line 10: 0line 11: 2line 13: 28line 14: 36line 16: 39line 17: 47line 19: 50line 22: 58line 23: 66
}
如圖,與第一次的字節(jié)碼相比,在標(biāo)號36、47都有了goto指令
。如果case 0
匹配成功,則跳到標(biāo)號28執(zhí)行,執(zhí)行完代碼塊對應(yīng)的31、33指令之后,執(zhí)行36的goto指令
跳轉(zhuǎn)到標(biāo)號58,這樣就跳出了switch
作用范圍,case 1和2也不會被執(zhí)行。
在Java字節(jié)碼中,goto指令用于無條件跳轉(zhuǎn)到指定的目標(biāo)代碼塊。它可以實現(xiàn)程序的跳轉(zhuǎn)和循環(huán)控制。
等等,怎么少了一個goto,在標(biāo)號58的上方應(yīng)該還有一個goto才對!
其實這就涉及到了編譯器優(yōu)化技術(shù),最后一個goto也是跳轉(zhuǎn)到標(biāo)號58的指令,但沒有g(shù)oto下一步也一樣順序執(zhí)行此行指令,所以這個goto被編譯器視為無用代碼進(jìn)行了消除。
3. switch和if的區(qū)別
先用if
實現(xiàn)上面switch
邏輯。
public class Test {public static void main(String[] args) {int i = 0;if (i == 0) {System.out.println(0);} else if (i == 1) {System.out.println(1);} else if (i == 2) {System.out.println(2);}System.out.println("Hello World");}
}
編譯成字節(jié)碼
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: ifne 166: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;9: iconst_010: invokevirtual #3 // Method java/io/PrintStream.println:(I)V13: goto 4316: iload_117: iconst_118: if_icmpne 3121: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;24: iconst_125: invokevirtual #3 // Method java/io/PrintStream.println:(I)V28: goto 4331: iload_132: iconst_233: if_icmpne 4336: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;39: iconst_240: invokevirtual #3 // Method java/io/PrintStream.println:(I)V43: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;46: ldc #4 // String Hello World48: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V51: return
}
「ifne」和「if_icmpne」是Java字節(jié)碼指令中的兩個條件分支指令,用于在程序執(zhí)行過程中進(jìn)行條件判斷并跳轉(zhuǎn)到相應(yīng)的代碼塊。它們的區(qū)別在于操作數(shù)類型和比較方式。
ifne
:操作數(shù)類型為int,功能是當(dāng)棧頂元素不等于零時,跳轉(zhuǎn)到指定的代碼塊。if_icmpne
:操作數(shù)類型為int,當(dāng)兩個int類型的數(shù)值不相等時,跳轉(zhuǎn)到指定的代碼塊。
從字節(jié)碼也可以看出if
和switch
的區(qū)別:
if
條件和代碼塊的字節(jié)碼是順序的,switch
條件和代碼塊是分開的;if
自動生成goto
指令,switch
只有加了break
才生成goto
指令。
4. 總結(jié)
case穿透現(xiàn)象
:指在switch語句中,當(dāng)某個case
語句沒有break
,程序會繼續(xù)執(zhí)行下一個case
語句。case
中的break
作用是告訴前端編譯器:「給每個case
對應(yīng)代碼塊的最后加上goto
」。這樣,執(zhí)行完匹配上的代碼之后,就可以略過后面的case
代碼塊了。switch
都支持哪些類型呢?- 基本數(shù)據(jù)類型:byte, short, char, int
- 包裝數(shù)據(jù)類型:Byte, Short, Character, Integer
- 枚舉類型:Enum
- 字符串類型:String(Jdk 7+ 開始支持)