引言

  switch 语句是非常的基础的知识,掌握起来也不难掌握,语法比较简单。但大部分人基本是知其然,不知其所以然。譬如 早期JDK只允许switch的表达式的值 int及int类型以下的基本类型,后期的JDK却允许匹配比较 字符串、枚举类型,这是怎么做到的呢?原理是什么?本文将深入去探索。

一、switch 介绍

switch 语法格式:

  1. switch (表达式) {
  2. case 常量表达式或枚举常量:
  3. 语句;
  4. break;
  5. case 常量表达式或枚举常量:
  6. 语句;
  7. break;
  8. ......
  9. default: 语句;
  10. break;
  11. }

switch 匹配的表达式可以是:

  • byte、short、char、int类型及 这4种类型的包装类型;
  • 枚举类型;
  • String 类型;

case 匹配的表达式可以是:

  • 常量表达式;
  • 枚举常量;

注意一点: case提供了switch表达式的入口地址,一旦switch表达式与某个case分支匹配,则从该分支的语句开始执行,一直执行下去,即其后的所有case分支的语句也会被执行,直到遇到break语句。

看个例子体会一下:

  1. public static void main(String[] args) {
  2. String s = "a";
  3. switch (s) {
  4. case "a": //a分支
  5. System.out.println("匹配成功1");
  6. case "b": //b分支
  7. System.out.println("匹配成功2");
  8. case "c": //c分支
  9. System.out.println("匹配成功3");
  10. break;
  11. case "d": //d分支
  12. System.out.println("匹配成功4");
  13. break;
  14. default:
  15. break;
  16. }
  17. }

运行结果:

匹配成功1

匹配成功2

匹配成功3

  switch成功匹配了a分支,但a、b分支都没有 break 语句,所以一直执行a分支后的所有语句,直到遇到c分支的break语句才终止。

二、编译器对 switch 表达式的各种类型的处理

  尽管 switch 支持的类型扩充了几个,但其实在底层中,swtich 只能支持4种基本类型,其他几个类型是通过一些方式来间接处理的,下面便是讲解编译器对扩充类型的处理。

1、对包装类的处理

  对包装类的处理是最简单的 —— 拆箱。看下面的例子,switch 比较的是包装类 Byte 。

  1. Byte b = 2;
  2. switch (b) {
  3. case 1:
  4. System.out.println("匹配成功");
  5. break;
  6. case 2:
  7. System.out.println("匹配成功");
  8. break;
  9. }

用jad反编译一下这段代码,得到的代码如下:

  1. Byte b = Byte.valueOf((byte)2);
  2. switch(b.byteValue())
  3. {
  4. case 1: // '\001'
  5. System.out.println("\u5339\u914D\u6210\u529F");
  6. break;
  7. case 2: // '\002'
  8. System.out.println("\u5339\u914D\u6210\u529F");
  9. break;
  10. }

  反编译的代码很简单,底层的switch比较的是Byte通过(拆箱)方法byteValue()得到的byte值。顺便说一下,这段反编译代码不仅揭开了 拆箱 的解析原理,也展示了 装箱 的解析原理(第一句代码);

2. 枚举类型

为了简单起见,直接采用JDK提供的枚举类型的线程状态类 Thread.state 类。

  1. Thread.State state = Thread.State.RUNNABLE;
  2. switch (state) {
  3. case NEW:
  4. System.out.println("线程处于创建状态");
  5. break;
  6. case RUNNABLE:
  7. System.out.println("线程处于可运行状态");
  8. break;
  9. case TERMINATED:
  10. System.out.println("线程结束");
  11. break;
  12. default:
  13. break;
  14. }

反编译代码:

  1. Sex sex = Sex.MALE;
  2. switch($SWITCH_TABLE$Test_2018_1_14$Sex()[sex.ordinal()])
  3. {
  4. case 1: // '\001'
  5. System.out.println("sex:male");
  6. break;
  7. case 2: // '\002'
  8. System.out.println("sex:female");
  9. break;
  10. }

  从编译代码中发现,编译器对于枚举类型的处理,是通过创建一个辅助数组来处理,这个数组是通过一个$SWITCH_TABLE$java$lang$Thread$State() 方法创建的,数组是一个int[]类型数组,数组很简单,在每个枚举常量的序号所对应的数组下标位置的赋一个值,按序号大小赋值,从1开始递增。 其代码如下:

  1. //int 数组
  2. private static int $SWITCH_TABLE$java$lang$Thread$State[];
  3. //创建数组的方法
  4. static int[] $SWITCH_TABLE$java$lang$Thread$State()
  5. {
  6. $SWITCH_TABLE$java$lang$Thread$State;
  7. if($SWITCH_TABLE$java$lang$Thread$State == null) goto _L2; else goto _L1
  8. _L1:
  9. return;
  10. _L2:
  11. JVM INSTR pop ;
  12. int ai[] = new int[Thread.State.values().length];
  13. try
  14. {
  15. ai[Thread.State.BLOCKED.ordinal()] = 3;
  16. }
  17. catch(NoSuchFieldError _ex) { }
  18. try
  19. {
  20. ai[Thread.State.NEW.ordinal()] = 1;
  21. }
  22. catch(NoSuchFieldError _ex) { }
  23. try
  24. {
  25. ai[Thread.State.RUNNABLE.ordinal()] = 2;
  26. }
  27. catch(NoSuchFieldError _ex) { }
  28. try
  29. {
  30. ai[Thread.State.TERMINATED.ordinal()] = 6;
  31. }
  32. catch(NoSuchFieldError _ex) { }
  33. try
  34. {
  35. ai[Thread.State.TIMED_WAITING.ordinal()] = 5;
  36. }
  37. catch(NoSuchFieldError _ex) { }
  38. try
  39. {
  40. ai[Thread.State.WAITING.ordinal()] = 4;
  41. }
  42. catch(NoSuchFieldError _ex) { }
  43. return $SWITCH_TABLE$java$lang$Thread$State = ai;
  44. }
  45. }

3、 对String类型的处理

依旧是先看个例子,再查看这个例子反编译代码,了解编译器的是如何解析的。

  1. public static void main(String[] args) {
  2. String s = "China";
  3. switch (s) {
  4. case "America":
  5. System.out.println("匹配到美国");
  6. break;
  7. case "China":
  8. System.out.println("匹配到中国");
  9. break;
  10. case "Japan":
  11. System.out.println("匹配到日本");
  12. default:
  13. break;
  14. }
  15. }

反编译得到的代码:

  1. public static void main(String args[])
  2. {
  3. String s = "China";
  4. String s1;
  5. switch((s1 = s).hashCode())
  6. {
  7. default:
  8. break;
  9. case 65078583:
  10. if(s1.equals("China"))
  11. System.out.println("\u5339\u914D\u5230\u4E2D\u56FD");
  12. break;
  13. case 71341030:
  14. if(s1.equals("Japan"))
  15. System.out.println("\u5339\u914D\u5230\u65E5\u672C");
  16. break;
  17. case 775550446:
  18. if(s1.equals("America"))
  19. System.out.println("\u5339\u914D\u5230\u7F8E\u56FD");
  20. break;
  21. }
  22. }

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

总结:

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

java基础(六) switch语句的深入解析的更多相关文章

  1. 【Java基础】switch语句实现根据数字输出对应星期

    代码: import java.util.Scanner; /* * switch语句格式: * switch(表达式) { * case 值1: * 语句体1; * break; * case 值2 ...

  2. Java基础(1):Switch语句注意的5个地方

    不得不说的几点小秘密: 1. switch 后面小括号中表达式的值必须是整型或字符型 2. case 后面的值可以是常量数值,如 1.2:也可以是一个常量表达式,如 2+2 :但不能是变量或带有变量的 ...

  3. Java中的switch语句后面的控制表达式的数据类型

    Java中的switch语句后面的控制表达式的数据类型 1.byte 2.char 3.short 4.int 5.枚举类型 6.Java 7允许java.lang.String类型

  4. java基础2 判断语句:if ... else 语句和 switch 语句

    一.if ... else 判断语句 1.if ... else 判断语句的格式 1.1.格式一 if(判断条件){ 执行不满足条件的语句 } 1.2.格式二 if(判断语句){ 满足条件的语句 }e ...

  5. Java基础之循环语句、条件语句、switch case 语句

    Java 循环结构 - for, while 及 do...while 顺序结构的程序语句只能被执行一次.如果您想要同样的操作执行多次,,就需要使用循环结构. Java中有三种主要的循环结构: whi ...

  6. java基础-控制流程语句

    一 前言 周末睡觉好舒服,都不想动了,就想睡睡,晒晒太阳,作者劳碌命还是过来写文章了.基础系列文章已经已经出到控制流程,感觉也挺快的,我很自信全网没都多少系列文章能有我这基础系列写的这么好,易于初学者 ...

  7. java基础3 循环语句:While 循环语句、do while 循环语句、 for 循环语句 和 break、continue关键字

    一.While循环语句 1.格式 while(条件表达式){ 执行语句: } 2.要点 1,先判断后执行 2,循环次数不定 3,避免死循环 3.举例 题目1:输出0-100之间的所有数 class D ...

  8. JavaSE入门学习7:Java基础语法之语句(下)

    继续接着Java基础语法来:JavaSE入门学习5:Java基础语法(一)和JavaSE入门学习6:Java基础语法(二). 语句 Java经常使用的3种循环:while.do...while,for ...

  9. Java基础之开关语句详解

    switch 语句是单条件多分支的开关语句,它的一般格式定义如下(其中break语句是可选的): switch(表达式) { case 常量值: 若干个语句 break; case  常量值: 若干个 ...

随机推荐

  1. 如何上传webshell后改回原来的webshell的格式

    一般后台不给允许上传php,asp格式的东东 所以我们要把木马改为jpg格式 记录下上传的路径 我们上传后木马因为格式不对不能被正确解析,我们可以利用网站的备份数据库模式恢复格式 在备份数据库那填上我 ...

  2. 使用Vue快速开发单页应用

    本文所涉及代码全在vue-cnode 单页应用,即在一个页面集成系统中所有功能,整个应用只有一个页面.因为路由的控制在前端,单页面应用在页面切换时比传统页面更快,从而在前端体验更好. 将逻辑从后端转移 ...

  3. CSS的box-sizing属性

    box-sizing属性可以为三个值之一:content-box(default),border-box,padding-box. content-box,border和padding不计算入widt ...

  4. 蓝桥杯模拟赛-引爆炸弹-DFS+并查集

    今天整理电脑,翻出来了很久以前大佬给的题,贴一下. 引爆炸弹 1000ms 在一个 n×m的方格地图上,某些方格上放置着炸弹.手动引爆一个炸弹以后,炸弹会把炸弹所在的行和列上的所有炸弹引爆,被引爆的炸 ...

  5. BZOJ2338: [HNOI2011]数矩形

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2338 中学数学老师告诉我们,一个矩形的两条对角线相等,所以只要把所有的边拿出来,记录下中点坐标 ...

  6. [国嵌攻略][162][USB协议分析]

    USB设备逻辑结构 在USB设备的逻辑组织中,包含设备.配置.接口和端点4个层次.设备通常有一个或多个配置,配置通常有一个或多个接口,接口通常有零个或多个端点. USB设备描述符 当我们把USB设备( ...

  7. 启动tomcat时,一直卡在Deploying web application directory这块的解决方案

    本来今天正常往服务器上扔一个tomcat 部署一个项目的, 最后再启动tomcat 的时候 发现项目一直都访问不了,看了一下日志: [root@iz8vbdzx7y7owm488t4d89z bin] ...

  8. 把VueThink整合到已有ThinkPHP 5.0项目中

     享 关键字: VueThink ThinkPHP5.0 Vue2.x TP5 管理后台扩展 VueThink初认识 VueThink,是一个很不错的技术框架,由广州洪睿科技的技术团队2016年研发( ...

  9. MySql Host is blocked because of many connection errors;

    错误:Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' 原因: 同一个i ...

  10. dedecms_插件

    ../dede/adbaoming.php../dede/baoming_edit.php../dede/templets/baoming_main.htm