天天看點

誰是代碼界3%的王者?- 第三題switch問題簡單解讀

 一、背景

阿裡技術的公衆發了一篇文章

《誰是代碼界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");
        }
    }
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

我想大多人在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.11
switch的表達式必須是

char

,

byte

short

int

Character

Byte

Short

Integer

String

, or an enum類型, 否則會發生編譯錯誤

switch語句必須滿足以下條件,否則會出現編譯錯誤:

  • 與switch語句關聯的每個case都必須和switch的表達式的類型一緻。
  • 如果

    switch表達式是

    枚舉類型,  

    case

    常量也必須是枚舉類型.
  • 不允許同一個

    switch的

    兩個

    case常量的值相同

    .
  • 和switch語句關聯的常量不能為

    null

  • 一個switch語句最多有一個default标簽

When the

switch

statement is executed, first the Expression is evaluated. If the Expression evaluates to

null

, a

NullPointerException

is thrown and the entire

switch

statement completes abruptly for that reason. 

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");
        }
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

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
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

關鍵點

第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
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

根據第7行可知先調用了param.hashCode()函數,然後将參數的hashCode和case比,

如果上面看不懂沒關系,重點看這裡:

switch語句表達式大緻等價于

String param = null;
int hashCode = param.hashCode();
if(hashCode==("null").hashCode()&&param.equals("null")){     
   System.out.println("null");
   
}else{
   System.out.println("default");
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

顯然param.hashCode()這裡會空指針。

另外我們列印

System.out.println(("null").hashCode());      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

發現結果果然是: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");
        }
    }
}      
誰是代碼界3%的王者?- 第三題switch問題簡單解讀

答案是"null",這也側面證明了官方文檔所說的先執行swtich的”表達式“,對于String而言調用了hashCode函數。

另外指派語句為啥有傳回值的問題參見:

《Java指派語句的傳回值》

五、總結

俗話說”授人以魚不如授人以漁“,本文講述了三個主要方法一個是看源碼,一個是看官方文檔,一個是反彙編。

另外我們學習Java程式設計的時候,遇到沒把握的問題說明知識學的不夠透徹,應該抓住時機搞透對應的知識點。

看一些常見的基礎圖書入門以後,後面有時間一定要多看官方文檔,多看源碼。

有時間要學習Java反彙編指令,從更底層來學習Java程式設計語言。

創作不易,如果覺得本文對你有幫助,歡迎點贊,歡迎關注我,如果有補充歡迎評論交流,我将努力創作更多更好的文章。