揭秘Java switch语句中的case穿透现象
揭秘Java switch语句中的case穿透现象
- 1. switch 语句简介
- 2. case穿透现象的原因
- 关于 goto
- 3. switch和if的区别
- 4. 总结
导语:在 Java 开发中,我们经常使用switch语句来进行条件判断和分支选择。然而,有一个令人困惑的现象就是,当某个case语句没有加上break关键字时,程序会继续执行下一个case语句,这被称为case穿透现象。本文将揭秘case穿透现象的原因,并解释为何会出现这种行为。
1. switch 语句简介
在开始揭秘case穿透现象之前,我们先简单回顾一下switch语句的基本用法。switch语句用于根据变量的不同取值执行相应的代码块。其语法结构如下:
switch (expression) {case value1:// 执行代码块1break;case value2:// 执行代码块2break;...default:// 默认代码块
}
switch case支持的6种数据类型:switch 表达式后面的数据类型只支持byte、short、int整形类型、字符类型char、枚举类型和java.lang.String类型。
根据expression的值,程序会跳转到对应的case语句进行匹配并执行相应的代码块,直到遇到break关键字或者到达switch语句的结尾。
如果某个case语句没有break,程序会继续执行下一个case语句,这就是case穿透现象。
我们看下面这个例子。
public class Test {public static void main(String[] args) {int i = 0;switch (i) {case 0:System.out.println("0");case 1:System.out.println("1");case 2:System.out.println("2");}}
}
打印结果:
0
1
2
2. case穿透现象的原因
按照惯用套路,看看字节码能不能给个答案。
javac编译和javap查看:
> javap -c -l Test.class
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: tableswitch { // 0 to 20: 281: 362: 44default: 52}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String 033: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;39: ldc #5 // String 141: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V44: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;47: ldc #6 // String 249: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V52: returnLineNumberTable:line 10: 0line 11: 2line 13: 28line 15: 36line 17: 44line 19: 52
}
根据提供的字节码,我们来解释一下case穿透的情况。
在main方法中,通过tableswitch指令实现了一个switch语句。switch语句会根据值进行跳转,并执行对应的代码块。
在这个例子中,根据tableswitch指令的参数 {0 to 2},case的范围是从0到2。
- 当
switch的表达式的值为0时,程序会跳转到标签为28的位置,然后继续执行28标签处的代码块。 - 为1时跳转到标号36代码处;
- 为2时跳转到标号44代码处;
- default则跳转到标号52代码处。
这不,答案就出来了,当case 0匹配了之后,直接跳转到标号28代码处开始执行,输出0,然后策马奔腾,一路下坡,顺序执行完后面所有代码,直到标号52 return,方法完执行完成,程序结束。
如果按照正常的思维,是不是case 0匹配之后,跳到28,执行完28、31、32输出0之后,就应该直接跳走,直接执行49。
那么,这个【跳走】用字节码应该怎么表示?
关于 goto
再写代码样例,这次我们在代码中给每个case都加上break。
public class Test {public static void main(String[] args) {int i = 0;switch (i) {case 0:System.out.println("0");break;case 1:System.out.println("1");break;case 2:System.out.println("2");break;}System.out.println("Hello World");}
}
打印结果:
0
Hello World
重新编译,再来看看字节码。
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: tableswitch { // 0 to 20: 281: 392: 50default: 58}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String 033: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: goto 5839: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;42: ldc #5 // String 144: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V47: goto 5850: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;53: ldc #6 // String 255: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V58: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;61: ldc #7 // String Hello World63: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V66: returnLineNumberTable:line 10: 0line 11: 2line 13: 28line 14: 36line 16: 39line 17: 47line 19: 50line 22: 58line 23: 66
}
如图,与第一次的字节码相比,在标号36、47都有了goto指令。如果case 0匹配成功,则跳到标号28执行,执行完代码块对应的31、33指令之后,执行36的goto指令跳转到标号58,这样就跳出了switch作用范围,case 1和2也不会被执行。
在Java字节码中,goto指令用于无条件跳转到指定的目标代码块。它可以实现程序的跳转和循环控制。
等等,怎么少了一个goto,在标号58的上方应该还有一个goto才对!
其实这就涉及到了编译器优化技术,最后一个goto也是跳转到标号58的指令,但没有goto下一步也一样顺序执行此行指令,所以这个goto被编译器视为无用代码进行了消除。
3. switch和if的区别
先用if实现上面switch逻辑。
public class Test {public static void main(String[] args) {int i = 0;if (i == 0) {System.out.println(0);} else if (i == 1) {System.out.println(1);} else if (i == 2) {System.out.println(2);}System.out.println("Hello World");}
}
编译成字节码
Compiled from "Test.java"
public class com.atu.algorithm.aTest.Test {public com.atu.algorithm.aTest.Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 8: 0public static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: ifne 166: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;9: iconst_010: invokevirtual #3 // Method java/io/PrintStream.println:(I)V13: goto 4316: iload_117: iconst_118: if_icmpne 3121: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;24: iconst_125: invokevirtual #3 // Method java/io/PrintStream.println:(I)V28: goto 4331: iload_132: iconst_233: if_icmpne 4336: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;39: iconst_240: invokevirtual #3 // Method java/io/PrintStream.println:(I)V43: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;46: ldc #4 // String Hello World48: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V51: return
}
「ifne」和「if_icmpne」是Java字节码指令中的两个条件分支指令,用于在程序执行过程中进行条件判断并跳转到相应的代码块。它们的区别在于操作数类型和比较方式。
ifne:操作数类型为int,功能是当栈顶元素不等于零时,跳转到指定的代码块。if_icmpne:操作数类型为int,当两个int类型的数值不相等时,跳转到指定的代码块。
从字节码也可以看出if和switch的区别:
if条件和代码块的字节码是顺序的,switch条件和代码块是分开的;if自动生成goto指令,switch只有加了break才生成goto指令。
4. 总结
case穿透现象:指在switch语句中,当某个case语句没有break,程序会继续执行下一个case语句。case中的break作用是告诉前端编译器:「给每个case对应代码块的最后加上goto」。这样,执行完匹配上的代码之后,就可以略过后面的case代码块了。switch都支持哪些类型呢?- 基本数据类型:byte, short, char, int
- 包装数据类型:Byte, Short, Character, Integer
- 枚举类型:Enum
- 字符串类型:String(Jdk 7+ 开始支持)
相关文章:
揭秘Java switch语句中的case穿透现象
揭秘Java switch语句中的case穿透现象 1. switch 语句简介2. case穿透现象的原因关于 goto 3. switch和if的区别4. 总结 导语:在 Java 开发中,我们经常使用switch语句来进行条件判断和分支选择。然而,有一个令人困惑的现象就是,当…...
Java-API简析_java.io.FilterOutputStream类(基于 Latest JDK)(浅析源码)
【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/134106510 出自【进步*于辰的博客】 因为我发现目前,我对Java-API的学习意识比较薄弱…...
C语言 每日一题 PTA 10.29 day7
1.特殊a串数列求和 给定两个均不超过9的正整数a和n,要求编写程序求a aa aaa⋯ aa⋯a(n个a)之和。 输入格式: 输入在一行中给出不超过9的正整数a和n。 输出格式: 在一行中按照“s 对应的和”的格式输出。 思路 n…...
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配 1. 安装 Ingress-Nginx2. 创建要代理的 Service3. 创建一个新的 Ingress-Nginx1. 安装 Ingress-Nginx 要使用 Ingress-Nginx 首先第一步是需要先安装它,安装的步骤可以参考:持续集成部署-k8s-服务发现-Ingress 2…...
selenium工作原理和反爬分析
一、 Selenium Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一,支持并行测试执行。Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ru…...
windows电脑安装系统后固态硬盘和机械硬盘的盘符号顺序显示错乱,解决方法
一、场景 由于电脑磁盘是SSD固态硬盘自己拓展的1T机械硬盘组成,固态硬盘分为C、D两个盘区,机械硬盘分为E、F两个盘区。为了提升运行速度,系统安装在C盘,安装完成后按照习惯盘区顺应该为C、D、E、F,但实际情况却是D、E…...
自定义控件的子控件布局(onLayout()方法)
onLayout()方法用于指定布局中子控件的位置,该方法通常在自定义的ViewGroup容器中重写。 重写onLayout()方法中的常用方法: getChildCount() 获取子控件数量 getChildAt( int index ) 获取指定index的子控件,返回View view.getVisibilit…...
vscode提取扩展出错xhr
在 Visual Studio Code (VSCode) 中提取扩展出现 XHR 错误通常意味着在下载扩展或进行扩展管理操作时出现了网络请求问题。XHR (XMLHttpRequest) 是一种用于在浏览器中进行 HTTP 请求的技术,通常用于获取数据或资源。在 VSCode 中,它也可用于管理扩展的下…...
Docker 笔记(上篇)
Docker 概述 Docker 概念 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之…...
python自动化测试(六):唯品会商品搜索-练习
目录 一、配置代码 二、操作 2.1 输入框“运动鞋” 2.2 点击搜索按钮 2.3 选择品牌 2.4 选择主款 2.5 适用性别 2.6 选择尺码 2.7 选择商品:(通过css的属性去匹配) 2.8 点击配送地址选项框 一、配置代码 # codingutf-8 from selen…...
深度强化学习用于博弈类游戏-基础测试与说明【1】
深度强化学习用于博弈类游戏-基础【1】 1. 强化学习方法2. 强化学习在LOL中的应⽤2.1 环境搭建2.2 游戏特征元素提取1)小地图人物位置:2)人物血量等信息3)在整个图像上寻找小兵、防御塔的位置4)自编码器提取3. 策略梯度算法简介参考资料1. 强化学习方法 伴随着人工智能的潮起…...
通过requests库使用HTTP编写的爬虫程序
使用Python的requests库可以方便地编写HTTP爬虫程序。以下是一个使用requests库的示例: import requests# 发送HTTP GET请求 response requests.get("http://example.com")# 检查响应状态码 if response.status_code 200:# 获取响应内容html response.…...
550MW发电机变压器组继电保护的整定计算及仿真
摘要 电力系统继电保护设计是根据系统接线图及要求选择保护方式,进行整定计算,电力系统继电保护的设计与配置是否合理直接影响到电力系统的安全运行。如果设计与配置不当,保护将不能正确工作,会扩大事故停电范围,造成…...
Linux 命令|服务器相关
1. 在公共 linux 上创建 python 虚拟环境 【精选】在公共Linux服务器上创建自己的python虚拟环境_服务器创建自己的环境-CSDN博客 2. 查看现存的状态,看有没有程序在跑 nvidia-smi命令详解-CSDN博客 3. 上传本地文件到服务器 在本地 Mac 计算机的终端中&#x…...
node 第十三天 express初见
express概念 Fast, unopinionated, minimalist web framework for Node.js 快速、独立、极简的 Node.js Web 框架。 express相当于前端的jquery, 在不更改不侵入原生node的基础上封装了大量易用且实用的服务端api, express框架的封装原理就是前面第十天我们自己封装的简易服务器…...
Python selenium模块简介
视频版教程:一天掌握python爬虫【基础篇】 涵盖 requests、beautifulsoup、selenium 有些网站的数据是js动态渲染的,我们无法通过网页源码直接找到数据,只能通过找接口方式来获取数据,但是很多时候,数据又是json格式的…...
DIY相机(一)libcamera库
相机选型 DIY相机首先是要确定使用的相机型号。兼容树莓派,画质好一些的,目前主要有两款:一是Raspberry Pi Camera Module 3,二是Raspberry Pi HQ Camera。 下图是Raspberry Pi Camera Module 3的相关特性。支持自动对焦和HDR等…...
PHP简单实现预定义钩子和自定义钩子
在PHP中,钩子(Hooks)是一种机制,允许开发人员在特定的时机插入自定义代码。通过使用钩子,开发人员可以在应用程序的特定事件发生时执行自定义的功能或逻辑 钩子有两种类型:预定义钩子和自定义钩子。 预定…...
笔记本电脑的摄像头找不到黑屏解决办法
这种问题一般来说就是缺少驱动,就要下载驱动。 问题: 解决办法: 1.进入联想官网下载驱动 网站:https://newsupport.lenovo.com.cn/driveDownloads_index.html?v9d9bc7ad5023ef3c3d5e3cf386e2f187 2.下载主机编号检测工具 3.下…...
【Git】HEAD detached from xxx 问题及解决方案
问题背景 最近用git的时候遇到了一个问题,场景是这样的。 我有一个分支main,其中有两个commit A和B,A是最新commit,B是历史commit。我先切到B看了看之前的代码,然后切到A,并进行了一些代码修改࿰…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
