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

    • 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 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)

大翔(CTO):咱们的支付系统里有一堆 if-else 判断消息类型,看着就头疼。

白歌(架构师):JDK 21 的 Switch 模式匹配可以彻底解决这个问题,配合 sealed class,编译器还能帮我们检查穷尽性。

小崔(后端开发):之前 switch 只能匹配常量,现在能直接匹配类型了?

孔蓝(后端新人):那 null 值怎么办?以前 switch 碰到 null 直接 NPE……


传统 switch 的局限性

局限性说明
只能匹配常量case 后只能是常量表达式(字面量、enum 常量、String 常量)
需要 break忘记写 break 会 fall through,引发逻辑错误
无法直接处理类型必须先 instanceof 再强转,代码冗长
不能处理 nullswitch (obj) 中 obj 为 null 直接抛 NPE
无穷尽性检查编译器无法确保你覆盖了所有情况

传统写法的问题示意:

// === 场景说明:传统写法,需要先 instanceof 再强转 ===
class TraditionalSwitch {
    public static void main(String[] args) {
        Object obj = "Hello";
        String result;

        if (obj instanceof String) {
            String s = (String) obj;
            result = "字符串,长度:" + s.length();
        } else if (obj instanceof Integer) {
            Integer i = (Integer) obj;
            result = "整数,值:" + i;
        } else if (obj == null) {
            result = "null 值";
        } else {
            result = "未知类型";
        }

        System.out.println(result);
    }
}

输出:

字符串,长度:5

问题:if-else 链冗长,instanceof 后还要强转,代码重复且不优雅。


Switch 模式匹配基础语法

概念定义
模式匹配 Switch在 case 标签中直接声明变量,编译器自动完成类型匹配和绑定
守卫条件(Guard)用 when 关键字在 case 后附加布尔条件,进一步细化匹配
穷尽性检查编译器确保所有可能的类型/值都被覆盖,否则编译报错

基本语法

// === 场景说明:使用模式匹配 switch 处理不同类型 ===
class SwitchPatternMatching {
    public static void main(String[] args) {
        Object obj = 42;

        String result = switch (obj) {
            case String s -> "字符串:" + s + ",长度:" + s.length();
            case Integer i -> "整数:" + i + ",平方:" + (i * i);
            case Double d -> "浮点数:" + d;
            case null, default -> "null 或其他类型";
        };

        System.out.println(result);
    }
}

输出:

整数:42,平方:1764

要点:模式变量(String s、Integer i)在 -> 右侧的作用域中自动可用,无需手动强转。


守卫条件(Guard Clauses)

JDK 20 引入 when 关键字,允许在 case 后附加条件。

语法说明
case Integer i when i > 0 -> ...仅当 i > 0 时匹配
case String s when s.length() > 5 -> ...可结合模式变量的属性
// === 场景说明:使用 when 守卫条件细化匹配 ===
class GuardClauseDemo {
    public static void main(String[] args) {
        Object obj = -10;

        String result = switch (obj) {
            case Integer i when i > 0 -> "正整数:" + i;
            case Integer i when i == 0 -> "零";
            case Integer i when i < 0 -> "负整数:" + i;
            case String s when s.isEmpty() -> "空字符串";
            case String s -> "非空字符串:" + s;
            case null -> "null";
            default -> "其他类型";
        };

        System.out.println(result);
    }
}

输出:

负整数:-10

null 处理

传统 switch 中,switch (obj) 若 obj 为 null 直接抛 NullPointerException。
模式匹配 switch 允许显式处理 null。

写法说明
case null -> ...显式匹配 null 值
case null, default -> ...null 和非匹配类型合并处理
// === 场景说明:显式处理 null 值,避免 NPE ===
class NullHandlingDemo {
    public static void main(String[] args) {
        Object obj = null;

        String result = switch (obj) {
            case null -> "⚠ 接收到 null 值,已安全处理";
            case String s -> "字符串:" + s;
            case Integer i -> "整数:" + i;
            default -> "其他类型:" + obj.getClass().getSimpleName();
        };

        System.out.println(result);
    }
}

输出:

⚠ 接收到 null 值,已安全处理

注意:如果 switch 块中没有处理 null 的 case,而传入 null,仍然会抛 NullPointerException。case null 是可选的,但建议显式处理。


多个模式合并

同一处理逻辑可以合并多个 case 标签,用逗号分隔。

// === 场景说明:多个类型共用同一处理逻辑 ===
class MultiPatternDemo {
    public static void main(String[] args) {
        Object obj = 3.14;

        String result = switch (obj) {
            case String s -> "文本类型:" + s;
            case Integer i, Double d -> "数值类型:" + obj;
            case Boolean b -> "布尔类型:" + b;
            case null -> "null";
            default -> "未处理的类型";
        };

        System.out.println(result);
    }
}

输出:

数值类型:3.14

穷尽性检查 + Sealed Class

概念定义
穷尽性检查(Exhaustiveness)编译器验证 switch 覆盖了所有可能的情况,未覆盖则编译报错
Sealed Class限制哪些类可以继承/实现该类,与 switch 模式匹配配合可实现编译期穷尽性验证

Sealed Class 语法回顾

// === 场景说明:定义密封的事件体系 ===
// Event.java
sealed interface Event permits OrderEvent, PaymentEvent, RefundEvent { }

final class OrderEvent implements Event {
    private final String orderId;
    public OrderEvent(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
}

final class PaymentEvent implements Event {
    private final double amount;
    public PaymentEvent(double amount) { this.amount = amount; }
    public double getAmount() { return amount; }
}

final class RefundEvent implements Event {
    private final String reason;
    public RefundEvent(String reason) { this.reason = reason; }
    public String getReason() { return reason; }
}

配合 Switch 模式匹配的穷尽性

// === 场景说明:sealed class + switch,编译器确保穷尽性 ===
class EventProcessor {
    public static void main(String[] args) {
        Event event = new PaymentEvent(199.99);

        String result = switch (event) {
            case OrderEvent e   -> "处理订单:" + e.getOrderId();
            case PaymentEvent e -> "处理支付,金额:" + e.getAmount();
            case RefundEvent e  -> "处理退款,原因:" + e.getReason();
            // 无需 default!编译器知道 Event 只有 3 个子类,已全部覆盖
        };

        System.out.println(result);
    }
}

输出:

处理支付,金额:199.99

如果新增子类但未更新 switch:编译器会报错,提示 switch 表达式未覆盖所有情况,在编译期发现错误,而非运行期。


实战场景:飞翔科技 Kafka 消息处理器

大翔(CTO):我们的 Kafka 消费者需要处理订单、支付、退款三种消息,用 sealed class + switch 模式匹配来路由。

完整实现

// === 场景说明:飞翔科技 Kafka 消息处理器,使用 sealed class + switch 模式匹配 ===

// 消息基类(密封接口)
sealed interface KafkaMessage permits OrderMsg, PaymentMsg, RefundMsg {
    String getTopic();
}

// 订单消息
record OrderMsg(String orderId, double amount, String topic) implements KafkaMessage {
    public OrderMsg {
        if (amount < 0) throw new IllegalArgumentException("金额不能为负");
    }
}

// 支付消息
record PaymentMsg(String paymentId, double amount, String topic) implements KafkaMessage { }

// 退款消息
record RefundMsg(String orderId, String reason, String topic) implements KafkaMessage { }

// 消息处理器
class KafkaMessageHandler {

    public static String handle(KafkaMessage message) {
        return switch (message) {
            case OrderMsg m when m.amount() > 10000 ->
                "🚨 大额订单告警,订单号:" + m.orderId() + ",金额:" + m.amount();
            case OrderMsg m ->
                "📦 处理订单,订单号:" + m.orderId() + ",金额:" + m.amount();
            case PaymentMsg m when m.amount() <= 0 ->
                "❌ 支付金额异常:" + m.paymentId();
            case PaymentMsg m ->
                "💰 处理支付,支付号:" + m.paymentId() + ",金额:" + m.amount();
            case RefundMsg m ->
                "↩️ 处理退款,订单号:" + m.orderId() + ",原因:" + m.reason();
        };
    }

    public static void main(String[] args) {
        KafkaMessage msg1 = new OrderMsg("ORD-20240601-001", 299.00, "order.topic");
        KafkaMessage msg2 = new OrderMsg("ORD-20240601-002", 25000.00, "order.topic");
        KafkaMessage msg3 = new PaymentMsg("PAY-001", 199.99, "payment.topic");
        KafkaMessage msg4 = new RefundMsg("ORD-20240601-001", "商品质量问题", "refund.topic");

        System.out.println(handle(msg1));
        System.out.println(handle(msg2));
        System.out.println(handle(msg3));
        System.out.println(handle(msg4));
    }
}

输出:

📦 处理订单,订单号:ORD-20240601-001,金额:299.0
🚨 大额订单告警,订单号:ORD-20240601-002,金额:25000.0
💰 处理支付,支付号:PAY-001,金额:199.99
↩️ 处理退款,订单号:ORD-20240601-001,原因:商品质量问题

与 JDK 14 Switch 表达式的对比

特性JDK 14 Switch 表达式JDK 21 Switch 模式匹配
箭头语法 ->✅ 支持✅ 支持
返回值✅ 可作为表达式返回值✅ 支持
类型匹配❌ 不支持✅ case String s ->
守卫条件 when❌ 不支持✅ 支持
null 处理❌ 不支持✅ case null ->
穷尽性(sealed)❌ 不支持✅ 编译器检查
多模式合并❌ 不支持✅ case A, B ->

易错场景

错误 1:忘记处理 null

// ❌ 错误:未处理 null,传入 null 会抛 NPE
class NullMissed {
    public static void main(String[] args) {
        Object obj = null;
        String result = switch (obj) {
            case String s -> s;
            case Integer i -> String.valueOf(i);
            default -> "other";
        };
        System.out.println(result);
    }
}
// 运行结果:Exception in thread "main" java.lang.NullPointerException
// ✅ 正确:显式处理 null
class NullHandled {
    public static void main(String[] args) {
        Object obj = null;
        String result = switch (obj) {
            case null -> "null 已处理";
            case String s -> s;
            case Integer i -> String.valueOf(i);
            default -> "other";
        };
        System.out.println(result);
    }
}
// 运行结果:null 已处理

错误 2:守卫条件中的变量作用域

// ❌ 错误:when 中引用了未绑定的变量
// case Integer i when s.length() > 0 -> ...  // s 在此分支不存在
// ✅ 正确:when 中只使用当前 case 绑定的变量
class GuardScope {
    public static void main(String[] args) {
        Object obj = "Hello";
        String result = switch (obj) {
            case String s when s.length() > 3 -> "长字符串:" + s;
            case String s -> "短字符串:" + s;
            default -> "其他";
        };
        System.out.println(result);
    }
}
// 运行结果:长字符串:Hello

错误 3:穷尽性不足(非 sealed 类型)

// ❌ 错误:Object 类型有无数种可能,编译器无法确保穷尽,必须加 default
class NotExhaustive {
    public static void main(String[] args) {
        Object obj = 1;
        // 编译错误:the switch expression does not cover all possible input values
        // String result = switch (obj) {
        //     case String s -> s;
        //     case Integer i -> String.valueOf(i);
        // };
    }
}
// ✅ 正确:加 default 分支确保穷尽
class ExhaustiveFixed {
    public static void main(String[] args) {
        Object obj = 1;
        String result = switch (obj) {
            case String s -> s;
            case Integer i -> String.valueOf(i);
            default -> "未匹配的类型:" + obj.getClass().getSimpleName();
        };
        System.out.println(result);
    }
}
// 运行结果:1

面试考点

问题 1:Switch 模式匹配与传统 switch 相比有哪些核心改进?

答:核心改进有五点:

  1. 类型模式匹配:case String s 直接在 case 中完成类型匹配和变量绑定,无需先 instanceof 再强转。
  2. 守卫条件:when 关键字允许在 case 后附加布尔表达式,进一步细化匹配条件。
  3. null 安全:case null -> 可以显式处理 null 值,避免隐式 NPE。
  4. 多模式合并:case Integer i, Double d -> 允许多个类型共用同一处理逻辑。
  5. 穷尽性检查:配合 sealed class,编译器在编译期确保所有可能类型都被覆盖,无需手写 default。

问题 2:sealed class 结合 switch 模式匹配的优势是什么?

答:优势体现在三个方面:

  1. 编译期安全:新增子类时,若未同步更新 switch 分支,编译器直接报错,将错误消灭在编译期。
  2. 无需 default:当 switch 覆盖了 sealed 接口的所有 permits 子类时,编译器认为已穷尽,可以不写 default,避免 default 吞掉新增类型的处理逻辑。
  3. 代码即文档:sealed 声明明确了类型的扩展边界,switch 分支与 permits 列表一一对应,可读性极强。

问题 3:when 守卫条件与在 case 右侧写 if 有什么区别?

答:区别在于匹配阶段和穷尽性影响:

  1. 匹配阶段:when 是在模式匹配成功后、进入分支前评估的,不匹配的会继续尝试下一个 case;而 if 是在分支内部判断,不匹配则需要通过 break 或 return 跳出。
  2. 穷尽性:when 守卫的分支仍被视为对该类型的一种匹配尝试,编译器在检查穷尽性时会考虑所有 case;if-else 在分支内部不影响 switch 的匹配结构。
  3. fall-through 语义:when 不匹配时自动尝试下一个 case,更符合 switch 的匹配语义;if 需要手动控制流程。

总结

白歌(架构师):用 sealed class 定义封闭类型体系,用 switch 模式匹配做路由,编译期就能发现遗漏的分支,这才是类型安全的代码。

孔蓝(后端新人):那我以后写消息处理器,再也不用担心漏掉某个类型了!

大翔(CTO):记住一句话——让编译器帮你干活,而不是让 Bug 在运行期帮你干活。

"Programs must be written for people to read, and only incidentally for machines to execute." —— Harold Abelson, 《Structure and Interpretation of Computer Programs》

上一页
序列集合(JDK 21 正式)
下一页
Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)