java基礎(六) switch語句的深入解析


引言

  switch 語句是非常的基礎的知識,掌握起來也不難掌握,語法比較簡單。但大部分人基本是知其然,不知其所以然。譬如 早期JDK只允許switch的表達式的值 int及int類型以下的基本類型,后期的JDK卻允許匹配比較 字符串、枚舉類型,這是怎么做到的呢?原理是什么?本文將深入去探索。

一、switch 介紹

switch 語法格式:

 switch (表達式) {
		case 常量表達式或枚舉常量:
			語句;
			break;
		case 常量表達式或枚舉常量:
			語句;
			break;
		......
		default: 語句;
			break;
	}

switch 匹配的表達式可以是:

  • byte、short、char、int類型及 這4種類型的包裝類型;
  • 枚舉類型;
  • String 類型;

case 匹配的表達式可以是:

  • 常量表達式;
  • 枚舉常量;

注意一點: case提供了switch表達式的入口地址,一旦switch表達式與某個case分支匹配,則從該分支的語句開始執行,一直執行下去,即其后的所有case分支的語句也會被執行,直到遇到break語句。

看個例子體會一下:

public static void main(String[] args) {
		String  s = "a";
		
        switch (s) {
		case "a": //a分支
			 System.out.println("匹配成功1");
			    
		case "b": //b分支
		        System.out.println("匹配成功2");
		        
		case "c": //c分支
		         System.out.println("匹配成功3");
		         break;
		case "d": //d分支
		         System.out.println("匹配成功4");
		         break;
		default:
			break;
		}
	}

運行結果:

匹配成功1
匹配成功2
匹配成功3

  switch成功匹配了a分支,但a、b分支都沒有 break 語句,所以一直執行a分支后的所有語句,直到遇到c分支的break語句才終止。

二、編譯器對 switch 表達式的各種類型的處理

  盡管 switch 支持的類型擴充了幾個,但其實在底層中,swtich 只能支持4種基本類型,其他幾個類型是通過一些方式來間接處理的,下面便是講解編譯器對擴充類型的處理。

1、對包裝類的處理

  對包裝類的處理是最簡單的 —— 拆箱。看下面的例子,switch 比較的是包裝類 Byte 。

		Byte b = 2;

		switch (b) {

		case 1:
			System.out.println("匹配成功");
			break;
		case 2:
			System.out.println("匹配成功");
			break;
		}

用jad反編譯一下這段代碼,得到的代碼如下:

        Byte b = Byte.valueOf((byte)2);

        switch(b.byteValue())
        {
        case 1: // '\001'
            System.out.println("\u5339\u914D\u6210\u529F");
            break;

        case 2: // '\002'
            System.out.println("\u5339\u914D\u6210\u529F");
            break;
        }

  反編譯的代碼很簡單,底層的switch比較的是Byte通過(拆箱)方法byteValue()得到的byte值。順便說一下,這段反編譯代碼不僅揭開了 拆箱 的解析原理,也展示了 裝箱 的解析原理(第一句代碼);

2. 枚舉類型

為了簡單起見,直接采用JDK提供的枚舉類型的線程狀態類 Thread.state 類。

	Thread.State state = Thread.State.RUNNABLE;
		
	switch (state) {
	case NEW:
		System.out.println("線程處於創建狀態");
		break;
	case RUNNABLE:
		System.out.println("線程處於可運行狀態");
		break;
	case TERMINATED:
		System.out.println("線程結束");
		break;

	default:
		break;
}

反編譯代碼:

Sex sex = Sex.MALE;
        switch($SWITCH_TABLE$Test_2018_1_14$Sex()[sex.ordinal()])
        {
        case 1: // '\001'
            System.out.println("sex:male");
            break;

        case 2: // '\002'
            System.out.println("sex:female");
            break;
        }

  從編譯代碼中發現,編譯器對於枚舉類型的處理,是通過創建一個輔助數組來處理,這個數組是通過一個$SWITCH_TABLE$java$lang$Thread$State() 方法創建的,數組是一個int[]類型數組,數組很簡單,在每個枚舉常量的序號所對應的數組下標位置的賦一個值,按序號大小賦值,從1開始遞增。 其代碼如下:

//int 數組
private static int $SWITCH_TABLE$java$lang$Thread$State[];

//創建數組的方法
static int[] $SWITCH_TABLE$java$lang$Thread$State()
    {
        $SWITCH_TABLE$java$lang$Thread$State;
        if($SWITCH_TABLE$java$lang$Thread$State == null) goto _L2; else goto _L1
_L1:
        return;
_L2:
        JVM INSTR pop ;
        int ai[] = new int[Thread.State.values().length];
        try
        {
            ai[Thread.State.BLOCKED.ordinal()] = 3;
        }
        catch(NoSuchFieldError _ex) { }
        try
        {
            ai[Thread.State.NEW.ordinal()] = 1;
        }
        catch(NoSuchFieldError _ex) { }
        try
        {
            ai[Thread.State.RUNNABLE.ordinal()] = 2;
        }
        catch(NoSuchFieldError _ex) { }
        try
        {
            ai[Thread.State.TERMINATED.ordinal()] = 6;
        }
        catch(NoSuchFieldError _ex) { }
        try
        {
            ai[Thread.State.TIMED_WAITING.ordinal()] = 5;
        }
        catch(NoSuchFieldError _ex) { }
        try
        {
            ai[Thread.State.WAITING.ordinal()] = 4;
        }
        catch(NoSuchFieldError _ex) { }
        return $SWITCH_TABLE$java$lang$Thread$State = ai;
    }
}

3、 對String類型的處理

依舊是先看個例子,再查看這個例子反編譯代碼,了解編譯器的是如何解析的。

public static void main(String[] args) {
		String  s = "China";
		
        switch (s) {
		case "America": 
			     System.out.println("匹配到美國");
			     break;
		case "China": 
		        System.out.println("匹配到中國");
		        break;
		case "Japan": 
		         System.out.println("匹配到日本");
		default:
			break;
		}
	}

反編譯得到的代碼:

  public static void main(String args[])
    {
        String s = "China";
        String s1;
        switch((s1 = s).hashCode())
        {
        default:
            break;

        case 65078583: 
            if(s1.equals("China"))
                System.out.println("\u5339\u914D\u5230\u4E2D\u56FD");
            break;

        case 71341030: 
            if(s1.equals("Japan"))
                System.out.println("\u5339\u914D\u5230\u65E5\u672C");
            break;

        case 775550446: 
            if(s1.equals("America"))
                System.out.println("\u5339\u914D\u5230\u7F8E\u56FD");
            break;
        }
    }

  從反編譯的代碼可以看出,switch 的String變量、case 的String常量都變成對應的字符串的 hash 值。也就是說,switch仍然沒有超出它的限制,只是通過使用 String對象的hash值來進行匹配比較,從而支持 String 類型。

總結:

  • 底層的switc只能處理4個基本類型的值。其他三種類型需要通過其他方式間接處理,即轉成基本類型來處理。
  • 編譯器對包裝類的處理是通過 拆箱。
  • 對枚舉類型的處理,是通過枚舉常量的序號及一個數組。
  • 對字符串String的處理,是通過 String 的hash值。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM