zoukankan      html  css  js  c++  java
  • 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: // '01'
                System.out.println("u5339u914Du6210u529F");
                break;
    
            case 2: // '02'
                System.out.println("u5339u914Du6210u529F");
                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: // '01'
                System.out.println("sex:male");
                break;
    
            case 2: // '02'
                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("u5339u914Du5230u4E2Du56FD");
                break;
    
            case 71341030: 
                if(s1.equals("Japan"))
                    System.out.println("u5339u914Du5230u65E5u672C");
                break;
    
            case 775550446: 
                if(s1.equals("America"))
                    System.out.println("u5339u914Du5230u7F8Eu56FD");
                break;
            }
        }
    

      从反编译的代码可以看出,switch 的String变量、case 的String常量都变成对应的字符串的 hash 值。也就是说,switch仍然没有超出它的限制,只是通过使用 String对象的hash值来进行匹配比较,从而支持 String 类型。

    总结:

    • 底层的switc只能处理4个基本类型的值。其他三种类型需要通过其他方式间接处理,即转成基本类型来处理。
    • 编译器对包装类的处理是通过 拆箱。
    • 对枚举类型的处理,是通过枚举常量的序号及一个数组。
    • 对字符串String的处理,是通过 String 的hash值。



  • 相关阅读:
    SpringCloud
    Linux
    SpringBoot
    秒杀系统设计
    设计模式
    数据库(mysql)
    Java web
    c#常用控件及简写
    C#常用的form窗体属性(最大化、最小化、窗体居中)
    C#中使用IndexOf()判断字符串在字符串数组中第一次出现的索引位置
  • 原文地址:https://www.cnblogs.com/jinggod/p/8425260.html
Copyright © 2011-2022 走看看