String类中有一个startsWith()函数,你一定使用过,它可以用于判断一个字符串是否是以
某个指定参数开头的。比如说下面这段代码的判断结果一定会是true:
if ("Hello Kotlin".startsWith("Hello")) { // 处理具体的逻辑
}
startsWith()函数的用法虽然非常简单,但是借助infix函数,我们可以使用一种更具可读
性的语法来表达这段代码。新建一个infix.kt 文件,然后编写如下代码:
infix fun String.beginsWith(prefix: String) = startsWith(prefix)
首先,除去最前面的infix关键字不谈,这是一个String类的扩展函数。我们给String类添
加了一个beginsWith()函数,它也是用于判断一个字符串是否是以某个指定参数开头的,并
且它的内部实现就是调用的String类的startsWith()函数。
但是加上了infix关键字之后,beginsWith()函数就变成了一个infix函数,这样除了传统
的函数调用方式之外,我们还可以用一种特殊的语法糖格式调用beginsWith()函数,如下所
示:
if ("Hello Kotlin" beginsWith "Hello") { // 处理具体的逻辑
}
从这个例子就能看出,infix函数的语法规则并不复杂,上述代码其实就是调用的" Hello
Kotlin "这个字符串的beginsWith()函数,并传入了一个"Hello"字符串作为参数。但是
infix函数允许我们将函数调用时的小数点、括号等计算机相关的语法去掉,从而使用一种更
接近英语的语法来编写程序,让代码看起来更加具有可读性。
另外,infix函数由于其语法糖格式的特殊性,有两个比较严格的限制:首先,infix函数是
不能定义成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某
个类当中;其次,infix函数必须接收且只能接收一个参数,至于参数类型是没有限制的。只
有同时满足这两点,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接口中
的contains()函数而已。也就是说,has()函数和contains()函数的功能实际上是一模一
样的,只是它多了一个infix关键字,从而拥有了infix函数的语法糖功能。
现在我们就可以使用如下的语法来判断集合中是否包括某个指定的元素:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
if (list has "Banana") { // 处理具体的逻辑
}
好了,两个例子都已经看完了,你对于infix函数应该也了解得差不多了。但是或许现在你的
心中还有一个疑惑没有解开,就是mapOf()函数中允许我们使用A to B这样的语法来构建键值
对,它的具体实现是怎样的呢?为了解开谜团,我们直接来看一看to()函数的源码吧,按住
Ctrl键(Mac 系统是command 键)点击函数名即可查看它的源码,如下所示:
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函数给我们带来的诸多有意思的功能,灵活运用它确实可以让语
法变得更具可读性。