原來你是這樣的switch~

Cympros發表於2019-01-19

在 switch-case 語句的條件判斷中,或許使用整形或者列舉更好,但由於種種歷史原因,專案中已大量使用字串的情況下,只得硬著皮頭往前衝了。對 switch 支援 String 的實現原理感興趣的原因,是在跟組員探討線上的一個空指標異常來的,以前根本沒意識到小小的switch 還有這樣的玩法。

要對 switch 的原理追根溯源,我們來寫一段簡單的 switch 程式碼,反編譯來看看位元組碼層是什麼效果。

public class Testk {
    public static void main(String[] args) {
        String key = null;
        switch (key) {
            case "java":
                System.out.println("caught java");
                break;
            case "android":
                System.out.println("caught android");
                break;
        }
    }
}

這裡我們定義引數key,根據 key 值跳轉不同的 case 邏輯。

正常情況下,使用 “javac <.java> ”生成.class 檔案,使用“javap -verbose <.class>”即可得到位元組碼,但由於javap得到的位元組碼結構難以理解,這裡我們使用 JD-Gui 工具來檢視。

Mac 下安裝JD-Gui工具

brew cask install jd-gui

把*.class 檔案拖入開啟的 JD-Gui視窗,即可得到如下結果:

import java.io.PrintStream;

public class Testk
{
  public static void main(String[] paramArrayOfString)
  {
    Object localObject1 = null;
    Object localObject2 = localObject1;int i = -1;
    switch (((String)localObject2).hashCode())
    {
    case 3254818: 
      if (((String)localObject2).equals("java")) {
        i = 0;
      }
      break;
    case -861391249: 
      if (((String)localObject2).equals("android")) {
        i = 1;
      }
      break;
    }
    switch (i)
    {
    case 0: 
      System.out.println("caught java");
      break;
    case 1: 
      System.out.println("caught android");
    }
  }
}

通過編譯後的程式碼,我們知道 switch 處理字串是先獲取hashCode ->equals()來實現的。

看到這裡,我們明白文首的空指標是怎麼來的了,編譯器針對 switch 的 String 做編譯處理時, 需要針對 key 做做非空校驗
另外,這裡先基於 hashCode()再 equals()方法進行安全檢查是有必要的,用來避免 hash 碰撞。

網上很多人都不建議使用字串,給出的理由多半是String 的大小寫使得程式碼更脆弱。在我看來,程式碼的脆弱多數是研發人員的程式碼風格不規範導致。
就拿上面的程式碼片段來說,改成全域性定義變數即可解決大小寫敏感問題。

public class Testk {
    private static final String KEY_JAVA=“java”;
    private static final String KEY_ANDROID=“android”;

    public static void main(String[] args) {
        String key = null;
        switch (key) {
            case KEY_JAVA:
                System.out.println("caught java");
                break;
            case KEY_ANDROID:
                System.out.println("caught java");
                break;
        }
    }
}

相關文章