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值。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/16375.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论