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);
   }

   @NotNull
   public static final Pair with(Object $this$with, Object that) {
      return new Pair($this$with, that);
   }
}

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

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值