我们在Kotlin中就多次使用A to B这样的语法结构构建键值对,包括Kotlin自带的mapOf()函数,这种语法结构的优点是可读性强。那么这种功能是怎么实现的?to是不是Kotlin语言中的一个关键字?本章我们来对这个功能进行解密。
首先,to并不是Kotlin语言中的一个关键字,之所以我们能使用A to B这样的语法结构,是因为Kotlin提供了一种高级语法糖特性:infix函数。infix函数只是把编程语言函数调用的语法规则调整了一些而已,比如A to B这样的写法,实际上等价于A.to(B)的写法。
通过两个具体的例子来学习一下infix函数的用法。
String类中有一个startsWith()函数,它可以用于判断一个字符串是否以某个指定参数开头的。比如说下面这段代码的判断结果一定会是true:
if("Hello Kotlin".startsWith("Hello")){
//处理具体的逻辑
}
startsWith()函数的用法虽然比较简单,但是借助infix函数,我们可以使用一种更具可读性的语法来表达这段代码。新建一个infix.kt文件,然后编写如下代码:
infix fun String.beginWith(prefix:String)=startsWith(prefix)
首先,除去最前面的infix关键字,这是一个String类的扩展函数。我们给String类添加一个beginsWith()函数,它也是用于判断一个字符串是否以某个指定参数开头的,并且它的内部实现就是调用的String类的startsWith()函数。
但是加上了infix关键字后,beginsWith()函数就变成了infix函数,这样除了传统的函数调用方式之外,我们还可以用一种特殊的语法糖格式调用beginsWith()函数,如下所示:
if("Hello Kotlin" beginWith "Hello"){
//处理具体逻辑
}
从这个例子就可以看出,infix函数的语法规则并不复杂,上述代码其实就是调用的"Hello Kotlin"这个字符串的beginWith()函数,并传入了一个"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函数的参数是泛型,所以has()函数可以接收任意具体类型的参数。而这个函数内部的实现逻辑就是调用了Collection接口中的contains()函数而已。也就是说,has()函数和contains()函数的功能是一模一样的,只是它多了一个infix关键字,从而拥有了infix函数的语法糖功能。
现在我们就可以使用如下的语法来判断集合中是否包括某个指定的元素:
val list=listOf("Apple","Banana","Orange","Pear","Grape")
if(list has "Banana"){
//处理具体逻辑
}
还有就是mapOf()函数中允许我们使用A to B这样的语法来构建键值对,它的具体实现是怎样的呢?我们通过查看源码如下:
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()函数来构建键值对了,还可以将键值对传入maoOf()方法中:
val map=mapOf("Apple" with 1,"Banana" with 2,"Orange" with 3,"Pear" with 4,"Grape" with 5)
这样通过灵活的使用infix函数,我们可以让语法变得更具可读性。