乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

switch 多分支选择

"优雅的代码不是炫技,而是在正确的场景使用正确的结构。" —— 白歌在审查小崔的绩效评级模块时说道,"当你需要基于单个变量的多个等值进行分支时,switch 比层层 if-else 更清晰,也更高效。"


概述

当程序需要在多个互斥的选项中根据某个变量的值选择一条执行路径时,switch 语句是比 if-else if-else 更优雅的选择。JDK 8 中 switch 支持四种类型:int、char、String、enum。

switch vs if-else if-else 定位

维度switchif-else if-else
判断方式等值匹配(== / equals)任意布尔表达式
适用场景离散值多分支选择范围判断、复杂条件
性能(编译期常量)O(1) tableswitchO(n) 逐个判断
可读性(多分支)优差(if-else 链过长)

语法格式

switch (表达式) {
    case 常量值1:
        // 语句块
        break;   // 跳出 switch
    case 常量值2:
        // 语句块
        break;
    ...
    default:
        // 所有 case 都不匹配时执行
        break;
}

关键点表格

关键点说明
表达式类型JDK 8:byte/short/int/char 及其包装类、String、enum
case 常量必须是编译期常量(字面量或 final 变量),不能重复
break跳出 switch 语句块;若省略则发生穿透(fall-through)
default可选;所有 case 都不匹配时执行;建议放在最后
JDK 7+ String 支持编译器将 switch(String) 转换为基于 hashCode 的 if-else + switch(int)

执行流程


示例一:绩效评级分类(int 类型)

白歌为飞翔科技制定了一套绩效评级规则,小崔负责在系统中实现:

/**
 * 飞翔科技绩效评级——switch(int)
 * 评级标准:A(90+) / B(80-89) / C(70-79) / D(60-69) / F(<60)
 */
public class PerformanceGrade {
    public static void main(String[] args) {
        String employee = "朱璐";
        int score = 87; // 综合评分

        // 将百分制转换为等级制
        int gradeLevel = score / 10;

        System.out.println("===== 飞翔科技绩效评级 =====");
        System.out.println("员工:" + employee);
        System.out.println("评分:" + score);

        switch (gradeLevel) {
            case 10: // 100分
            case 9:  // 90-99分
                System.out.println("绩效等级:A — 卓越");
                System.out.println("奖金系数:1.5x");
                break;
            case 8:  // 80-89分
                System.out.println("绩效等级:B — 优秀");
                System.out.println("奖金系数:1.2x");
                break;
            case 7:  // 70-79分
                System.out.println("绩效等级:C — 良好");
                System.out.println("奖金系数:1.0x");
                break;
            case 6:  // 60-69分
                System.out.println("绩效等级:D — 需改进");
                System.out.println("奖金系数:0.8x");
                break;
            default: // <60分
                System.out.println("绩效等级:F — 不合格");
                System.out.println("奖金系数:0x(无奖金)");
                break;
        }

        System.out.println("评级完成。");
    }
}
运行输出:
===== 飞翔科技绩效评级 =====
员工:朱璐
评分:87
绩效等级:B — 优秀
奖金系数:1.2x
评级完成。

技巧:case 10: case 9: 连续写两个 case(中间无 break),利用合法的穿透实现 100 分和 90-99 分共用同一处理逻辑。


示例二:星期几判断(String 类型)

JDK 7+ 支持 switch 直接使用 String,编译器会自动转换为 int 比较。Frank 需要根据星期几安排不同的团队会议:

/**
 * 飞翔科技会议室安排——switch(String)
 */
public class MeetingSchedule {
    public static void main(String[] args) {
        String dayOfWeek = "周三";

        System.out.println("===== 飞翔科技会议安排 =====");

        switch (dayOfWeek) {
            case "周一":
                System.out.println("上午 9:00 — 周例会(全体)");
                System.out.println("下午 2:00 — 技术评审会");
                break;
            case "周二":
                System.out.println("上午 10:00 — 产品需求讨论");
                break;
            case "周三":
                System.out.println("下午 3:00 — 代码走查(Code Review)");
                System.out.println("下午 5:00 — 技术分享(本周:JVM 调优)");
                break;
            case "周四":
                System.out.println("上午 10:00 — 架构评审会");
                System.out.println("下午 2:00 — 跨部门协调会");
                break;
            case "周五":
                System.out.println("下午 4:00 — 团队复盘 & Happy Hour");
                break;
            case "周六":
            case "周日":
                System.out.println("周末 — 无会议安排,请享受休息日!");
                break;
            default:
                System.out.println("无效的日期:" + dayOfWeek);
                break;
        }

        System.out.println("会议安排查询完毕。");
    }
}
运行输出:
===== 飞翔科技会议安排 =====
下午 3:00 — 代码走查(Code Review)
下午 5:00 — 技术分享(本周:JVM 调优)
会议安排查询完毕。

示例三:订单状态流转(enum 类型)

使用 enum 作为 switch 表达式是 JDK 5+ 的推荐实践——类型安全,不会传入非法值:

/**
 * 飞翔科技电商订单状态——switch(enum)
 */
enum OrderStatus {
    PENDING,    // 待支付
    PAID,       // 已支付
    SHIPPED,    // 已发货
    DELIVERED,  // 已签收
    CANCELLED,  // 已取消
    REFUNDED    // 已退款
}

public class OrderStateMachine {
    public static void main(String[] args) {
        OrderStatus status = OrderStatus.SHIPPED;

        System.out.println("===== 飞翔科技订单系统 =====");
        System.out.print("当前状态:" + status + " → ");

        switch (status) {
            case PENDING:
                System.out.println("操作:请尽快完成支付(30分钟内有效)");
                break;
            case PAID:
                System.out.println("操作:仓库备货中,预计24小时内发货");
                break;
            case SHIPPED:
                System.out.println("操作:物流运输中,物流单号:SF1234567890");
                System.out.println("操作:" + OrderStatus.DELIVERED + " — 用户可确认收货");
                break;
            case DELIVERED:
                System.out.println("操作:订单已完成,支持7天无理由退货");
                break;
            case CANCELLED:
                System.out.println("操作:订单已取消,款项将在3个工作日内退回");
                break;
            case REFUNDED:
                System.out.println("操作:退款已到账,订单流程结束");
                break;
            // default 可省略——enum 覆盖了所有可能的值
        }

        System.out.println("状态查询完毕。");
    }
}
运行输出:
===== 飞翔科技订单系统 =====
当前状态:SHIPPED → 操作:物流运输中,物流单号:SF1234567890
操作:DELIVERED — 用户可确认收货
状态查询完毕。

深度原理:tableswitch 与 lookupswitch

这是面试中区分优秀候选人的关键问题。switch 在字节码层面有两种实现方式,JVM 根据 case 的密集程度自动选择:

两种指令对比

特性tableswitchlookupswitch
适用场景case 值连续或接近连续case 值稀疏
查找方式直接计算偏移量,O(1)二分查找,O(log n)
内存占用需维护完整的跳转表仅存储 case 值列表
字节码结构跳转表 + 默认偏移(key, offset) 对列表

示例:tableswitch(密集 case)

public static String getSeason(int month) {
    switch (month) {
        case 3: case 4: case 5:  return "春季";
        case 6: case 7: case 8:  return "夏季";
        case 9: case 10: case 11: return "秋季";
        case 12: case 1: case 2:  return "冬季";
        default: return "无效月份";
    }
}

反编译字节码(javap -c):

tableswitch   { // 1 to 12
         1: 48      // month=1 → 偏移量48
         2: 48      // month=2 → 偏移量48
         3: 36      // month=3 → 偏移量36
         4: 36
         5: 36
         6: 44
         7: 44
         8: 44
         9: 52
        10: 52
        11: 52
        12: 48
     default: 60
}

访问方式:目标偏移 = jump_table[month - low],一次数组访问即可定位,时间复杂度 O(1)。

示例:lookupswitch(稀疏 case)

public static String getPlanetOrder(int planetCode) {
    switch (planetCode) {
        case 1:  return "水星";
        case 3:  return "地球";
        case 7:  return "天王星";
        case 8:  return "海王星";
        case 100: return "自定义天体";
        default: return "未知";
    }
}

反编译字节码:

lookupswitch  { // 5
         1: 28     // (key=1, offset=28)
         3: 34     // (key=3, offset=34)
         7: 40
         8: 46
       100: 52
     default: 58
}

JVM 选择策略:当 high - low + 1(case 范围)与 case 数量之比不太大时,使用 tableswitch;否则使用 lookupswitch 节省空间。

String switch 的底层实现

JDK 7+ 的 switch(String) 是一个语法糖,编译器将其拆解为两个阶段:

编译器将原始代码:

switch (day) {
    case "周一": ...
    case "周二": ...
}

转换为等效的:

int hashCode = day.hashCode();
switch (hashCode) {
    case 649148:    // "周一".hashCode()
        if (day.equals("周一")) { ... }
        break;
    case 650048:    // "周二".hashCode()
        if (day.equals("周二")) { ... }
        break;
}

这也是为什么 switch(String) 中 case 不能为 null —— null.hashCode() 会抛出 NullPointerException。


易错场景

反例一:忘记 break 导致的穿透(Fall-Through)

这是 switch 语句中最高发的错误。小崔初学时写了这样的代码:

// ❌ 错误:忘记 break,导致每个 case 都"穿透"到下一个
int weekday = 3;
switch (weekday) {
    case 1:
        System.out.println("周一");
    case 2:
        System.out.println("周二");
    case 3:
        System.out.println("周三");
    case 4:
        System.out.println("周四");
    case 5:
        System.out.println("周五");
}
运行输出(错误):
周三
周四
周五

原因:从匹配的 case 3 开始执行,因为没有 break,执行流"穿透"进入 case 4 和 case 5,直到 switch 结束或遇到 break。

// ✅ 正确:每个 case 末尾添加 break
int weekday = 3;
switch (weekday) {
    case 1:
        System.out.println("周一");
        break;
    case 2:
        System.out.println("周二");
        break;
    case 3:
        System.out.println("周三");
        break;
    case 4:
        System.out.println("周四");
        break;
    case 5:
        System.out.println("周五");
        break;
}
运行输出(正确):
周三

例外:有时我们需要利用穿透——比如多个 case 共享同一处理逻辑。此时应在代码注释中明确标注 // fall-through。

反例二:case 后使用了非常量表达式

// ❌ 编译错误:case 必须是编译期常量
int x = 3;
int y = 3;
switch (x) {
    case y:       // 编译错误:constant expression required
        System.out.println("匹配");
        break;
}

y 是变量,虽然值不变,但它不是编译期常量(未声明 final)。

// ✅ 正确:使用 final 常量
final int MONDAY = 1;
int day = 1;
switch (day) {
    case MONDAY:
        System.out.println("周一");
        break;
}

反例三:switch 表达式为 null

// ❌ 运行时错误:NullPointerException
String level = null;
switch (level) {   // 调用 level.hashCode() → NPE
    case "P5":
        System.out.println("中级");
        break;
}
// ✅ 正确:先判空
String level = null;
if (level == null) {
    System.out.println("职级未设定");
} else {
    switch (level) {
        case "P5":
            System.out.println("中级");
            break;
    }
}

反例四:重复的 case 值

// ❌ 编译错误:duplicate case label
int score = 85;
switch (score) {
    case 85:
        System.out.println("优秀");
        break;
    case 85:   // 编译错误!重复的 case 标签
        System.out.println("良好");
        break;
}

面试考点

Q1:switch 支持哪些数据类型?JDK 各版本的变化?

JDK 版本支持的类型
JDK 1.0byte, short, int, char
JDK 5增加 enum
JDK 7增加 String
JDK 12+switch 表达式(-> 箭头语法,不在本章范围)

不支持的类型:long、float、double、boolean。boolean 只有两个值,用 if-else 即可。

Q2:tableswitch 和 lookupswitch 的区别是什么?JVM 如何选择?

  • tableswitch:为连续范围内的每个值维护跳转表项,通过 value - min 直接索引,O(1) 时间。适合 case 值密集的场景。
  • lookupswitch:维护 (key, offset) 键值对列表,通过二分查找定位,O(log n) 时间。适合 case 值稀疏的场景。
  • 选择策略:JVM 规范未强制选择策略,由编译器实现决定。一般当 case 值的范围与数量之比小于一定阈值时用 tableswitch。

Q3:switch(String) 的实现原理是什么?

编译器将其拆解为两步:首先对 String 调用 hashCode(),然后用 switch(int) 匹配 hashCode 值。匹配到 hashCode 后,再用 equals() 做二次确认以防止哈希冲突。因此,switch(String) 的性能取决于 hashCode 的分布质量。

Q4:为什么 switch 不支持 long 类型?

switch 的设计初衷是高效的跳转表(tableswitch),需要一个可快速映射为数组索引的整数键。long 的取值范围太大(2^64),无法用跳转表高效覆盖。如果确实需要对 long 做多分支,使用 if-else 或 HashMap 分发。

Q5:switch 中的 default 可以放在任意位置吗?

可以放在任意位置(开头、中间、末尾),但强烈建议放在最后。如果放在前面且没有 break,会穿透到后续 case,极易造成逻辑错误。


小结

要点说明
适合等值判断switch 用于少量离散值的等值匹配,范围判断用 if-else
break 不可忘除非有意利用穿透,否则每个 case 末尾加 break
null 先判空switch(expr) 中 expr 为 null 会导致 NPE
case 值必须唯一编译器强制检查,重复会报错
tableswitch vs lookupswitch密集用 tableswitch(O(1)),稀疏用 lookupswitch(O(log n))
String 是语法糖编译器展开为 hashCode + equals 两步确认
enum 最安全结合 enum 使用 switch 可获得编译期类型安全检查
上一页
if-else 选择结构
下一页
while 循环