揭秘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,并进行了一些代码修改࿰…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
