Java 17 新特性— 模式匹配 的Switch 表达式

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

Switch 表达式

Java 12 引入 Switch 表达式,它解决了传统 Switch 语句的两个缺陷:

  1. "Fall-through" 行为:在没有显式 break 语句的情况下,Switch 语句会从一个 case "穿透" 到下一个 case,忽略了这个会导致不可饶恕的错误。
  2. 代码冗余:每个 case,我们都需要重复类似的代码结构,增加了代码的冗余和维护难度。

Switch 表达式引入了 -> 操作符,用于替代传统的冒号(:)。与传统的 Switch 语句不同,使用 -> 的 case 分支不会出现 "fall-through" 现象,因此不需要 break 语句来防止穿透,如下:

    public static String getTypeOfDay(String day) {
        String typeOfDay = switch (day) {
            case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
            case "SATURDAY", "SUNDAY" -> "Weekend";
            default -> "Unknown";
        };
        return typeOfDay;
    }

它虽然解决这两个问题,但是还是有一个不好的地方,就是返回值,比如在处理复杂的逻辑时,仍需依赖外部变量来返回结果,所以 Java 13 对 Switch 表达式进行了扩展,引入 yield关键字来处理多分支结构中的返回值,如下:

    public static String getTypeOfDay(String day) {
        return switch (day) {
            case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" ->  "Weekday";
            case "SATURDAY", "SUNDAY" ->  "Weekend";
            default -> {
                if (day.isEmpty()) {
                   yield "day is empty";
                } else {
                    yield "Unknown";
                }
            }
        };
    }

但是,不知道小伙伴们注意没有,Switch 表达式只有一种类型,如果我们要处理多种类型呢?我们只能这样处理 :

Object obj = ... // 某个对象

switch (obj.getClass().getName()) {
    case "java.lang.String":
        String str = (String) obj;
        // 处理字符串
        break;
    case "java.lang.Integer":
        Integer i = (Integer) obj;
        // 处理整数
        break;
    case "com.lang.Long":
        Long lon = (Long) obj;
        // 处理MyClass实例
        break;
    // 其他类型
}

这里面的强制转换是不是比较烦?而且有没有很熟悉 ?Java 14 引入的模式匹配是不是可以解决?

模式匹配

instanceof 用于检查一个对象是否是特定类的实例或者该类的子类的实例。它通常用在条件语句中,以确定对象的类型,从而避免在向下转型时发生 ClassCastException。在 Java 14 前,我们一般都是这样写:

if (object instanceof String) {
    // 返回 true,确认是 String 类型,强制转换为 String 类型后使用
    String str = (String) object;
}

if 语句里面的强制转换显得很是多余,所以 Java 14 引入模式匹配的 instanceof 来解决这个问题,它允许在 instanceof 操作符的条件判断中直接定义一个变量,如果对象是指定的类型,这个变量会被初始化为被检查的对象,可以立即使用,无需额外的类型转换,如:

if (obj instanceof String str) {
    // 可以直接使用 str,而不需要显式的类型转换
}

但是这里需要写大量的 if...else,如果它和上面的 Switch 表达式相结合是不是就可以产生不一样的化学反应?

更多阅读:Java 14 新特性—模式匹配的 instanceof

Switch 模式匹配

用于 instanceof 的模式匹配在Java 16 成为正式特性,到了 Java 17 模式匹配的应用扩展到了switch表达式,这标志着 Switch 表达式又得到了一次增强。

在 Java 17 中,switch 表达式允许使用模式匹配来处理对象类型,这样就可以直接在 switch 语句中检查和转换类型,而不需要额外的 if...else 结构和显式类型转换。

switch 模式匹配支持以下几种模式:

  1. 类型模式
  2. 空模式
  3. 守卫模式
  4. 常量模式

类型模式

这是一种比较常见的模式,它允许在 switch 语句的 case 分支中直接匹配对象的类型。例如,case String s 允许你在该分支中直接作为字符串类型的 s 来使用,避免了显式的类型检查和强制类型转换。举个例子来说明下:

    @Test
    public void switchTest() {
        Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
        for (Object obj: objects) {
            if (obj instanceof Integer intR) {
                System.out.println("为整数型:" + intR);
            } else if (obj instanceof Float floatR) {
                System.out.println("为浮点型:" + floatR);
            } else if (obj instanceof Double doubleR) {
            System.out.println("为双精度浮点数:" + doubleR);
            } else if (obj instanceof String str) {
                System.out.println("为字符串:" + str);
            } else {
                System.out.println("其他类型:" + obj);
            }
        }
    }

我们用 Switch 表达式来改造下:

    @Test
    public void switchTest() {
        Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
        for (Object obj: objects) {
            switch (obj) {
                case Integer intR -> System.out.println("为整数型:" + intR);
                case Float floatR -> System.out.println("为浮点型:" + floatR);
                case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
                case String str -> System.out.println("为字符串:" + str);
                default -> System.out.println("其他类型:" + obj);
            }
        }
    }

相比上面的 if...else 简洁了很多。同时在 Java 17 之前,Switch 选择器表达式只支持特定类型,即基本整型数据类型byteshortcharint;对应的装箱形式ByteShortCharacterIntegerString类;枚举类型。现在有了类型模式,Switch 表达式可以是任何类型啦。

空模式

在Java17之前,向switch语句传递一个null值,会抛出一个NullPointerException,现在可以通过类型模式,将 null 检查作为一个单独的case标签来处理,如下:

    @Test
    public void switchTest() {
        Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
        for (Object obj: objects) {
            switch (obj) {
                // 省略...
                case null -> System.out.println("为空值");
                default -> System.out.println("其他类型:" + obj);
            }
        }
    }

case null 可以直接匹配值为 null 的情况。

守卫模式

守卫模式允许我们在 case 标签后添加一个额外的条件。只有当类型匹配并且额外条件为真时,才会进入该 case 块。

比如上面例子,我们要将字符串那块逻辑调整下,比如长度大于 5 的为长字符串,小于 5 的为短字符串,在不使用守卫模式的情况下,我们一般这样写:

    @Test
    public void switchTest() {
        Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
        for (Object obj: objects) {
            switch (obj) {
                case Integer intR -> System.out.println("为整数型:" + intR);
                case Float floatR -> System.out.println("为浮点型:" + floatR);
                case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
                case String str -> {
                    if (str.length() > 5) {
                        System.out.println("为长字符串:" + str);
                    } else {
                        System.out.println("为短字符串:" + str);
                    }
                }
                case null -> System.out.println("为空值");
                default -> System.out.println("其他类型:" + obj);
            }
        }
    }

这种写法就显得不是那么友好,使用守卫模式如下:

    @Test
    public void switchTest() {
        Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
        for (Object obj: objects) {
            switch (obj) {
                case Integer intR -> System.out.println("为整数型:" + intR);
                case Float floatR -> System.out.println("为浮点型:" + floatR);
                case Double doubleR -> System.out.println("为双精度浮点数:" + doubleR);
                case String str && str.length() > 5 -> System.out.println("为长字符串:" + str);
                case String str -> System.out.println("为短字符串:" + str);
                case null -> System.out.println("为空值");
                default -> System.out.println("其他类型:" + obj);
            }
        }
    }

使用守卫模式,我们可以编写更灵活和表达性强的代码。

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值