9.5--Kotlin课堂:使用 infix 函数构建更可读的语法

在前面的章节中,我们已经多次使用过A to B 这样的语法结构构建键值对,包括Kotlin 自带的mapOf() 函数,以及我们在第7章中自己创建的cvOf() 函数。

这种语法结构的优点是可读性高,相比于调用一个函数,它更接近于使用英语的语法来编写程序。可能你会好奇,这种功能是怎么实现的呢? to 是不是Kotlin 语言中的一个关键字?本节的Kotlin 课堂中,我们就对这个功能进行深度解密。

首先,to 并不是 Kotlin 语言中的一个关键字,之所以我们能够使用 A to B 这样的语法结构,是因为Kotlin 提供了一种高级语法糖特性;infix 函数 。当然,infix 函数 也并不是什么难理解的事务,它只是把编程语言函数调用的语法规则调整了一下而已,比如A to B 这样的写法,实际上等价于 A.to(B) 的写法。

 下面我们就通过两个具体的例子来学习一下infix 函数的用法,先从简单的例子看起。

String类中有一个startsWith() 函数,你一定使用过,它可以用于判断一个字符串是否是以某个指定参数开头的。比如说下面这段代码的判断结果一定会是true:

if ("Hello Word".startsWith("Hello")){
            // 处理的逻辑
        }

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

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

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

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

if ("Hello Word" beginsWith  "Hello"){
            // 处理的逻辑
        }

从这个例子就能看书,infix 函数的语法规则并不复杂,上述代码其实就是调用的“Hello Word”这个字符串的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 接口中的contanins() 而以。也就是说has() 函数和contanins() 函数的功能实际上是一模一样的,只是它多了一个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() 函数的具体实现,非常简单,就是创建并返回了一个Pari 对象。也就是说,A to B 这样的语法结构实际上得到的是一个包含A 、B 数据的Par 对象,而 mapOf() 函数实际上接收的正是一个Pari类型的可变参数列表,这样我们接将这种神奇的语法结构完全解密了。

本着手动实际的精神,其实我们也可以模仿 to() 函数的源码来写一个自己的键值对构建函数,在infix 函数 文件中添加如下代码:

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 函数  给我们带来的诸多有意思的功能,灵活运用它确实可以让语法变得更具可读性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值