一、背景
阿裡技術的公衆發了一篇文章
《誰是代碼界3%的王者?》,
提到“在Java代碼界,有些陷阱外表看起來是個青銅實際上是王者,據說97%工程師會被“秒殺””
給出了五道題,非常考驗基礎。
本文簡單解讀第3題,并分享通用的學習和研究方法。
二、題目
這段代碼輸出的結果是:
A: null
B: 抛出異常
C: default
public static void main(String[] args) {
String param = null;
switch (param) {
case "null":
System.out.println("null");
break;
default:
System.out.println("default");
}
}
}

我想大多人在B和C猶豫不決。
因為我們學switch的時候沒專門有例子給出這種例子傳入null,且學switch的時候default表示不滿足其他case的時候會執行,是以猜測很可能列印default。
不過因為是null,會不會發生空指針呢?
我們運作就可以看到結果(空指針異常),但是我們下面從其他更權威的方法進行分析。
三、上法寶
3.1 源碼大法
和第五題的解析不同的是switch無法進入到其JDK”源碼“中,暫時放棄架構或JDK源碼大法。
3.2 官方文檔大法
https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.11switch的表達式必須是,
char
byte
short
int
Character
Byte
Short
Integer
, or an enum類型, 否則會發生編譯錯誤
String
switch語句必須滿足以下條件,否則會出現編譯錯誤:
When the
- 與switch語句關聯的每個case都必須和switch的表達式的類型一緻。
- 如果
枚舉類型,
switch表達式是
常量也必須是枚舉類型.
case
- 不允許同一個
兩個
switch的
.
case常量的值相同
- 和switch語句關聯的常量不能為
null
一個switch語句最多有一個default标簽
statement is executed, first the Expression is evaluated. If the Expression evaluates to
switch
, a
null
is thrown and the entire
NullPointerException
statement completes abruptly for that reason.
switch
語句執行的時候, 首先将執行switch的表達式.如果表達式為
switch
, 則會抛出
null
NullPointerException,整個switch語句的執行将被中斷
答案就顯而易見了,B抛出異常,且為空指針異常。
3.3 java反解析大法
我們先看一個正常的例子
public static void main(String[] args) {
String param = "t";
switch (param) {
case "a":
System.out.println("a");
break;
case "b":
System.out.println("b");
break;
case "c":
System.out.println("c");
break;
default:
System.out.println("default");
}
}
javap -c SwitchTest
對應的反彙編代碼(穩住!!看不懂不要方,後面有個簡化版):
Compiled from "SwitchTest.java"
public class com.chujianyun.common.style.SwitchTest {
public com.chujianyun.common.style.SwitchTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String t
2: astore_1
3: aload_1
4: astore_2
5: iconst_m1
6: istore_3
7: aload_2
8: invokevirtual #3 // Method java/lang/String.hashCode:()I
11: tableswitch { // 97 to 99
97: 36
98: 50
99: 64
default: 75
}
36: aload_2
37: ldc #4 // String a
39: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_3
47: goto 75
50: aload_2
51: ldc #6 // String b
53: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_3
61: goto 75
64: aload_2
65: ldc #7 // String c
67: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_3
75: iload_3
76: tableswitch { // 0 to 2
0: 104
1: 115
2: 126
default: 137
}
104: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
107: ldc #4 // String a
109: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
112: goto 145
115: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
118: ldc #6 // String b
120: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
123: goto 145
126: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
129: ldc #7 // String c
131: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
134: goto 145
137: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
140: ldc #10 // String default
142: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
145: return
}
關鍵點
第8行:調用t的hashCode擷取其哈希值。
第11行:計算swich的case的哈希值a 為97, b為98,c為99
依次執行到36行,50行和64行,default為75行。
依次判斷a.equals(param)是否為true,如果是則跳轉到列印的語句,然後再跳轉到145行退出;否則跳轉到default語句列印并退出。
在編譯題目的源碼:
Compiled from "SwitchTest.java"
public class com.chujianyun.common.style.SwitchTest {
public com.chujianyun.common.style.SwitchTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: aconst_null
1: astore_1
2: aload_1
3: astore_2
4: iconst_m1
5: istore_3
6: aload_2
7: invokevirtual #2 // Method java/lang/String.hashCode:()I
10: lookupswitch { // 1
3392903: 28
default: 39
}
28: aload_2
29: ldc #3 // String null
31: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
34: ifeq 39
37: iconst_0
38: istore_3
39: iload_3
40: lookupswitch { // 1
0: 60
default: 71
}
60: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
63: ldc #3 // String null
65: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
68: goto 79
71: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
74: ldc #7 // String default
76: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
79: return
}
根據第7行可知先調用了param.hashCode()函數,然後将參數的hashCode和case比,
如果上面看不懂沒關系,重點看這裡:
switch語句表達式大緻等價于
String param = null;
int hashCode = param.hashCode();
if(hashCode==("null").hashCode()&¶m.equals("null")){
System.out.println("null");
}else{
System.out.println("default");
}
顯然param.hashCode()這裡會空指針。
另外我們列印
System.out.println(("null").hashCode());
發現結果果然是:3392903
四、延伸
那我們看下面的代碼,結果是啥呢?
public class SwitchTest {
public static void main(String[] args) {
String param = null;
switch (param="null") {
case "null":
System.out.println("null");
break;
default:
System.out.println("default");
}
}
}
答案是"null",這也側面證明了官方文檔所說的先執行swtich的”表達式“,對于String而言調用了hashCode函數。
另外指派語句為啥有傳回值的問題參見:
《Java指派語句的傳回值》五、總結
俗話說”授人以魚不如授人以漁“,本文講述了三個主要方法一個是看源碼,一個是看官方文檔,一個是反彙編。
另外我們學習Java程式設計的時候,遇到沒把握的問題說明知識學的不夠透徹,應該抓住時機搞透對應的知識點。
看一些常見的基礎圖書入門以後,後面有時間一定要多看官方文檔,多看源碼。
有時間要學習Java反彙編指令,從更底層來學習Java程式設計語言。
創作不易,如果覺得本文對你有幫助,歡迎點贊,歡迎關注我,如果有補充歡迎評論交流,我将努力創作更多更好的文章。