当前位置: 首页 > news >正文

Kotlin 中的 infix 关键字(中缀函数)

在 Kotlin 中,infix 是一个关键字,用于定义中缀函数(Infix Functions)。

中缀函数允许我们在调用函数的时候使用更加简洁的中缀符号(通常是一个操作符),而不是传统的点符号调用方式。中缀函数的作用是使代码更具可读性,特别是在编写某些领域特定语言(DSL)时非常有用。

在 mapOf 函数中允许我们用 A to B 这样的语法来构建键值对,它的具体实现是怎样的呢?以下是 to 函数的源码:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

可以看出,这里使用定义泛型函数的方式将 to 函数定义到了 A 类型下,并且接收一个 B 类型的参数。因此 A 和 B 可以是两种不同类型的泛型,也就使得我们可以构建出字符串 to 整型这样的键值对。

再来看 to 函数的具体实现,非常简单,就是创建并返回一个 Pair 对象。也就是说, A to B 这样的语法结构实际上得到是一个包含 A、B 数据的 Pair 对象,而 mapOf 函数实际上接收的正式一个 Pair 类型的可变参数列表,这样我们就将这种神奇的语法结构完全解密了。

下面,我们模仿 to 函数的源码来编写一个自己的键值对构建函数,在 infix.kt 文件中添加如下代码:

infix fun <A, B> A.with(that: B): Pair<A, B> = Pair(this, that)

这里只是将 to 函数改名为 with 函数,其他的实现逻辑是相同的,因为相信没有什么解释的必要。现在我们的项目中就可以使用 with 函数来构建键值对了,还可以将构建的键值对传入 mapOf 方法中:

val map = mapOf("Apple" with 1, "Banana" with 2, "Orange" with 3, "Pear" with 4, "Grape" with 5
)

这就是 infix 函数(中缀函数),灵活运用它可以让语法变得更具可读性。

一个中缀函数的表达形式非常简单:

A 中缀方法 B

当我们要定义一个中缀函数的时候,它必须满足如下条件:

  • 该中缀函数必须是某个类型的扩展函数或者成员方法;
  • 该中缀函数只能有一个参数;
  • 虽然 Kotlin 的函数参数支持默认值,但中缀函数的参数不能有默认值,否则以上形式的 B 会缺失,从而对中缀表达式的语义造成破坏;

再比如,String 类中有一个 startWith 函数,用于判断一个字符串是否是以某个指定的参数开头的。比如下面这段代码的判断结果就是 true:

if ("Hello Kotlin".startsWith("Hello")){// 处理具体的逻辑
}

startWith 函数的用法虽然非常简单,但是借助 infix 函数,我们可以使用一种更具可读性的语法来表达这段代码。新建一个 infix.kt 文件:

infix fun String.beginsWith(prefix: String) = startsWith(prefix)

首先,去掉前面的 infix 关键字不谈,这就是一个 String 类的扩展函数。我们给 String 类添加了一个 beginsWith 函数,它也是用于判断一个字符串是否是以某个指定参数开头的,并且它的内部实现就是调用 String.startsWith 函数。

但是加了 infix 关键字之后,beginsWith 函数就变成了 infix 函数,这样除了传统的函数调用方法外,我们还可以用一种特殊的语法糖格式调用 beginsWith 函数,如下所示:

if ("Hello World" beginsWith("Hello")) { // 注意没有 .// 处理具体的逻辑
}

从这个例子中就能看出,infix 函数的语法规则并不复杂,上述的代码其实调用的 “Hello Kotlin” 这个字符的 beginsWith 函数,并传入 “Hello” 字符串作为参数。但是 infix 函数允许我们将函数调用的小数点、括号等计算机相关的语法去掉,从而使用一种更接近英语的语法来编写程序,使得代码看起来更具有可读性。

下面看一些复杂的例子。比如说这里有个集合,如果想要判断集合中是否包括某个指定元素,一般可以这样写:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
if (list.contains("Banana")) {// 处理具体的逻辑
}

我们仍然可以借助 infix 函数让这段代码变得更具可读性,在 infix.kt 文件中添加如下代码:

infix fun <T> Collection<T>.has(element: T) = contains(element)

可以看到,我们给 Collection 接口添加了一个扩展函数,这是因为 Collection 是 Java 以及 Kotlin 所有集合的总接口。因此给 Collection 添加了一个 has 函数,那么,所有集合的子类就都可以使用这个函数了。

另外,这里还用了泛型函数的定义方法,从而使得 has 函数可以接收任意类型具体类型的参数。而这个函数内部的实现就是调用了 Collection 接口中的 contians() 函数而已。也就是说,has 函数和 contains 函数的功能世纪上是一摸一样的,只是它多了一个 infix 关键字,从而拥有了 infix 函数的语法糖功能。

现在我们就可以使用如下的语法来判断集合中是否包括某个指定的元素:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
if (list has "Banana") {// 处理具体的逻辑
}

将 infix.kt 反编译成 Java 代码:

public final class InfixKt {public static final boolean beginsWith(@NotNull String $this$beginsWith, @NotNull String prefix) {Intrinsics.checkNotNullParameter($this$beginsWith, "$this$beginsWith");Intrinsics.checkNotNullParameter(prefix, "prefix");return StringsKt.startsWith$default($this$beginsWith, prefix, false, 2, (Object)null);}public static final boolean has(@NotNull Collection $this$has, Object element) {Intrinsics.checkNotNullParameter($this$has, "$this$has");return $this$has.contains(element);}@NotNullpublic static final Pair with(Object $this$with, Object that) {return new Pair($this$with, that);}
}

可以看到都是普通的函数调用,也就是说中缀函数的原理是编译器在语法层面给予了支持。Kotlin 的很多特性都是在语法和编译器上的优化。

相关文章:

  • C# 集合(二) —— List/Queue类
  • 马斯克的Grok-1:开源AI模型的突破与挑战
  • TrueNAS系统在ARM平台上的移植
  • 傅佩荣教授讲座视频全集,傅佩荣讲座大全,傅佩荣国学讲座全集百度网盘
  • 使用同步和异步方式更新插入MongoDB数据的性能对比
  • 使用Scala爬取安居客房产信息并存入CSV文件
  • AI时代:硬件狂欢,软件落寞 华为开发者大会2024
  • 如何在 MySQL 中创建和使用事务?
  • 一文读懂数据仓库ODS层
  • 外贸SEO工具有哪些推荐?
  • Unity URP下通过相机让部分Render不受后处理渲染
  • 前端模糊搜索关键字高亮
  • Dubbo3 服务原生支持 http 访问,兼具高性能与易用性
  • android Switch/case with R.id.XXXX in android doesn‘t work 错误: 需要常量表达式解决方案
  • 在超线程CPU上切换到另一个线程
  • C++入门教程(10):for 语句
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JS变量作用域
  • learning koa2.x
  • Linux gpio口使用方法
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • PHP的类修饰符与访问修饰符
  • storm drpc实例
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 学习Vue.js的五个小例子
  • 一、python与pycharm的安装
  • ###C语言程序设计-----C语言学习(6)#
  • #14vue3生成表单并跳转到外部地址的方式
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (2020)Java后端开发----(面试题和笔试题)
  • (二刷)代码随想录第15天|层序遍历 226.翻转二叉树 101.对称二叉树2
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (三)uboot源码分析
  • (十三)Flink SQL
  • (十一)手动添加用户和文件的特殊权限
  • (算法二)滑动窗口
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (微服务实战)预付卡平台支付交易系统卡充值业务流程设计
  • (小白学Java)Java简介和基本配置
  • (一)Java算法:二分查找
  • (转)四层和七层负载均衡的区别
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .Net MVC4 上传大文件,并保存表单
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .NET大文件上传知识整理
  • @ConfigurationProperties注解对数据的自动封装
  • @DS 多数据源 + @Transactional(rollbackFor = Exception.class) 导致@DS 多数据源没法使用