首页 > 其他分享 >instanceof 的模式匹配(一)

instanceof 的模式匹配(一)

时间:2024-11-15 18:43:53浏览次数:3  
标签:instanceof obj String objstr 作用域 Object 模式匹配

前言

相信你在Java编程中用到过如下的操作:

// 调用上游接口.返回结果obj
Object obj = getObj();
// 判断返回值是不是字符串
if (obj instanceof String) {
    String objstr = (String) obj;
    //  do something with objstr
}

以上这种instanceof-and-cast 惯用语的代码基本每个人都写过。这段代码的含义是:

这里有三件事:检查(obj 是 String 吗?)、转换(将 obj 转换为 String)以及声明新的局部变量(objstr),
以便我们可以使用 string 值来操作后续的逻辑。
此模式简单明了,所有 Java 程序员都理解,但由于多种原因,此模式并不理想。这样写很无聊;
应该没有必要同时执行类型测试和强制转换,因为作为正常人来说,你在instanceof检查之后,你还会做除了类型转换的其他事情吗。
同时,特别是 String 类型的三次出现更是混淆了后面更重要的逻辑。上来啥也没干,就写了三个String了,难怪别人说java语言是最繁琐的语言。
但最重要的是,重复写同一个东西为错误提供了机会,这些错误会在不被注意的情况下悄无声息地潜入程序中。繁琐意味着容易出错。

基于以上的问题,java在jdk14中提出了模式匹配的语法机制来解决这个问题。该语法在JEP305中发布,作为第一次预览(preview)java模式匹配

一、模式匹配的第一次预览

在jep305中发布的第一次模式匹配预览中,我们用他提供的语法来重写一下我们上面的例子。

// 调用上游接口.返回结果obj
Object obj = getObj();
// 判断返回值是不是字符串
if (obj instanceof String objstr) {
    //  do something with objstr
}

此时我们就看到,之前的1、检查 2、转换 3、创建新的变量 现在背缩减到了一步就完成了。编译通过,测试通过。
那么我们再把这个例子复杂化一点呢。我们增加一点逻辑。

public static void main(String[] args) {
    // 调用上游接口
    Object obj = getObj();
    // 判断返回值是不是字符串
    if (obj instanceof String objstr) {
        //  do something with objstr
        System.out.println(objstr.toUpperCase());
    }else {
        //  do something with other type
        // 这里将无法使用objstr变量,编译报错
    }
}

private static Object getObj() {
    return "hello";
}

我们在这个例子中看到了绑定变量objstr的作用范围,他仅仅在if为true的分支可以使用,而在false的分支就超出了该变量的作用域。注意这句话,仅仅在你判断为true的时候可以使用。
而当我们修改一下if中的判断逻辑,简单的加一个取反符号。

public static void main(String[] args) {
    // 调用上游接口
    Object obj = getObj();
    // 判断返回值是不是字符串
    if (!(obj instanceof String objstr)) {
        //  do something with objstr
        System.out.println(objstr.toUpperCase());// 这不可以使用objstr变量,编译报错
    }else {
        //  do something with other type
        // 这里可以使用objstr变量
        System.out.println(objstr.toUpperCase());
    }
}

我们看到我们就是加了一个取反符号,这个作用域就彻底反转过来了。所以他的变量范围只在true的if块中。当你的匹配表达式是false才能进入的分支的时候,该if中无法引用该变量。

而在得知了简单的作用域范围之后,我们再把if 语句的条件变得比单个 instanceof 更复杂时,他的对应的绑定变量的范围也会相应增加。例如,在此代码中:

public static void main(String[] args) {
    // 调用上游接口
    Object obj = getObj();
    // 判断返回值是不是字符串
    if (obj instanceof String objstr && objstr.length() > 2) {
        System.out.println(objstr.toUpperCase());
    }
}

private static Object getObj() {
    return "hello";
}

我们把条件变成了obj instanceof String objstr && objstr.length() > 2 这种复合条件,此时你可以看到,他整体还是可用的,并不会有编译错误。
那么我们再改一下变成这样。

if (obj instanceof String objstr || objstr.length() > 2) {
    System.out.println(objstr.toUpperCase());
}

我们仅仅是把条件中的&&变成了||,不好意思,此时报错了。
在这里插入图片描述
结合这两个案例,我们可以知道他的这种语法设计。那就是:

绑定变量 的作用域 位于 & & 运算符右侧的范围内,以及 true 块中。(仅当 instanceof 成功并分配给 s 时,才会对右侧进行评估,绑定变量 S 不在 ||运算符,也不在 true 块的范围内。

所以我们可以知道,他的作用是为了匹配类型,只有当他匹配类型成功之后,他才会为后面的分配作用域。
这个其实看着很晦涩,其实不难理解,我们还是最初那句话,因为作为正常人来说,你在instanceof检查之后,你还会做除了类型转换的其他事情吗。
所以我们一开始的出发点就是为了当你检查成功之后,我才会为你转换并且分配给一个变量中。
那么你检查不成功,我为啥还给你分配呢,直接就限制作用域得了。
所以我们来理解一下他的三种作用域范围。
第一种:if-else

// 调用上游接口
Object obj = getObj();
// 判断返回值是不是字符串
if (obj instanceof String objstr) {
    //  do something with objstr
    System.out.println(objstr.toUpperCase());
}else {
    //  do something with other type
    // 这里将无法使用objstr变量,编译报错
}
// 调用上游接口
Object obj = getObj();
// 判断返回值是不是字符串
if (!(obj instanceof String objstr)) {
    //  do something with objstr
    System.out.println(objstr.toUpperCase());// 这不可以使用objstr变量,编译报错
}else {
    //  do something with other type
    // 这里可以使用objstr变量
    System.out.println(objstr.toUpperCase());
}

这种匹配下,我们还是说我们是为了你检查成功然后为你分配的,当你在检查之后为false才会进入的地方没必要给你匹配这个作用域。只有当你obj instanceof String objstr 检查成功了,我们才会为你分配作用域范围。

第二种:

if (obj instanceof String objstr && objstr.length() > 2) {
    System.out.println(objstr.toUpperCase());
}

这种是没问题的,因为当你条件判断到objstr.length() > 2的时候,第一个条件obj instanceof String objstr已经必然为true了,所以符合我们的理解。

第三种:

if (obj instanceof String objstr || objstr.length() > 2) {
    System.out.println(objstr.toUpperCase());
}

这种就不行了,因为当你条件判断到objstr.length() > 2的时候,无法确认第一个条件obj instanceof String objstr是不是正确,这种无法分配。

于是我们可以总结一句作用域的理解口诀,只有当模式匹配为true的地方,才能使用模式绑定的变量。

二、带来的效果

在 instanceof 中使用模式匹配应该会大大减少 Java 程序中显式强制转换的总数。此外,类型测试模式在编写相等方法时特别有用。以下取自 Effective Java 书籍第 10 章的相等方法:

@Override 
public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString) && 
        ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 
}

此时你就能用模式匹配来优化一下了。我们可以改写为:

@Override 
public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString cis && 
        cis.s.equalsIgnoreCase(s)); 
}

是不是简洁干净了一些,起码不是那么恶心了。

三、展望

你能看到他在第一次预览的时候2017/05/30,其实只是简单的实现了一些语法性质的东西,后面发展了很多。我们会以这个模式匹配为一个系列来逐渐看到在jdk23乃至jdk25的时候,他的一个进步。
而当我们回顾历史的时候,我们把时间线拖回2017年,彼时java感受到了来自其他语言的威胁,开始做出改变,他没有选择出一个折中的方案来实现,而是直接彻底解决这个问题。并且作为上帝视角来看,在今天jdk23刚发布,在看模式匹配的时候,你能看到其布局之深远。
但是比较搞笑的是在jdk14的第一次预览截止到jdk15发布期间,这个特性并没有收到太多来自社区的反馈。于是官方在jdk15发布的时候又创建了JEP375,JDK15登场,模式匹配的第二次预览。期待收到更多的社区反馈,来做出改进。

标签:instanceof,obj,String,objstr,作用域,Object,模式匹配
From: https://blog.csdn.net/liuwenqiang1314/article/details/143682933

相关文章

  • Rust为什么要搞个match匹配,直接用==判断不行吗(Rust match、Rust ==、Rust模式匹配)
    文章目录1.模式匹配2.更强的类型安全和完整性检查3.解构能力4.清晰和简洁示例Rust中的match关键字和使用==直接进行判断有着不同的用途和优势。match是一种非常强大的控制流结构,用于模式匹配,它不仅可以用来检查等值关系,还能解构、比较和检查类型中的......
  • 使用 NLP 和模式匹配检测、评估和编辑日志中的个人身份信息 - 第 2 部分
    作者:来自Elastic StephenBrown如何使用Elasticsearch、NLP和模式匹配检测、评估和编辑日志中的PII。简介:分布式系统中高熵日志的普遍存在大大增加了PII(PersonallyIdentifiableInformation-个人身份信息)渗入我们日志的风险,这可能导致安全和合规性问题。这篇由两......
  • 单模式匹配 KMP 算法 简易版学习笔记
    KMP前缀函数设\(S_i\)为字符串\(S\)的第\(i\)个位置。我们设\(\pi(i)\)表示字符串以\(i\)结尾的前缀的最长公共前后缀的长度。这里的前后缀都指的是真前缀、真后缀。怎么\(O(n)\)求出\(\pi(i)\)。性质:相邻的\(\pi\)至多增加1。因此,若\(s[\pi(i)+1]=s[i+1......
  • 考研数据结构-串(串的模式匹配算法)
             串的基本操作可以参照考研数据结构-串(串的基本操作),除去这些基本操作外,还有一个重要的操作就是串的模式匹配算法。模式匹配算法就是,对于一个串中某个子串的定位操作,这里会讲两种模式匹配算法:简单模式匹配算法和KMP算法。一、简单模式匹配算法   简单......
  • JDK 21更新:switch语句的类型模式匹配与守卫模式
    Java语言自诞生以来,一直在不断演进,以满足开发者日益复杂的需求。switch语句作为一种控制流结构,在Java中有着广泛的应用。随着JDK21的发布,switch语句和表达式得到了显著增强,使其在处理复杂条件和类型检查方面更加灵活和强大。本文将详细探讨JDK21中switch语句和表达式的更......
  • AI 推理能力大“翻车”!苹果最新论文:LLM只是复杂的模式匹配,而不是真正的逻辑推理
    内容提要大语言模型真的可以推理吗?LLM都是“参数匹配大师”?苹果研究员质疑LLM推理能力,称其“不堪一击”!文章正文苹果的研究员MehrdadFarajtabar等人最近发表了一篇论文,对大型语言模型(LLM)的推理能力提出了尖锐的质疑,他认为,LLM的“推理”能力,其实只是复杂的模式匹......
  • JDK12~17的新特性:Switch增强,优化NPE,文本块,instanceof增强,record,sealed
    JDK14switch语句的增强:类似lambda的语法糖,不需要再写break了。提供yield实现返回值其中switch类型匹配属于预览,正常情况下是关闭的publicclassEnhanceSwitch{publicstaticvoidmain(String[]args){oldVersion();newVersion();}pri......
  • 全面解析 JDK17新特性:密封类、模式匹配、文本块、垃圾回收等最新功能详解
    引言JDK17作为Java的长期支持(LTS)版本,引入了许多新特性和改进,这些更新不仅提升了代码的可读性和可维护性,还增强了性能。本文将详细探讨JDK17的关键新特性,包括密封类、模式匹配、文本块、增强的垃圾回收机制等,并附上代码示例,帮助你更好地理解和应用这些特性。1.密封......
  • java-----instanceof与getClass的区别
    java-----instanceof与getClass的区别在比较一个类是否和另一个类属于同一个类实例的时候,我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断,但是两者在判断上面是有差别的,下面从代码中看看区别:publicclassTest{ publicstaticvoidtestInstanceof(Obj......
  • JAVA基础:抽象类,接口,instanceof,类关系,克隆
    1JDK中的包JDK=JRE+开发工具集(javac.exe)JRE=JVM+java类库JVM=java虚拟机jdk中自带了许多的包(类),常用的有java.lang该包中的类,不需要引用,可以直接使用。例如:Object,System,Stringjava.utiljava.sqljava.netjava.iojava.text2抽象方法......