Kotlin扩展函数
扩展函数可以称为Kotlin
的核心,标准库里到处充斥着扩展函数和高阶函数。然而标准库中扩展函数看起来都是高深莫测,一脸懵逼。本文教你如何理解扩展函数。
什么是扩展函数?
Kotlin
同C#
类似,能够扩展一个类的新功能而无需继承该类。通俗点讲就是使用扩展函数,可以为无法修改源代码的对象添加新的方法,或者强制让对象支持某些方法,添加之后这些方法看起来就是对象本来就有的功能。而在Java中我们通常使用写Utils
的形式来完成这项功能,而Kotlin
不需要。
声明扩展函数
声明一个扩展函数,我们需要指定一个接收者类型也就是被扩展的类型来作为他的前缀。例如我们可以对String
声明一个扩展函数为print()
:
1 | fun String.print() { |
扩展函数的声明是有作用域的,默认是当前包及子包可以免
import
使用。比如你在com.wastrel
包下声明的,那么com.wastrel.a
下也是可以使用的。当然你也可以对扩展函数加以private
等属性来限制其作用域。如果你的声明是public
(默认即是公有的),那么也可以在它默认作用域外使用improt
来导入使用。
泛型化扩展
有时候你可能需要对很多类进行扩展,这时候可以对扩展函数进行泛化,例如:
1 | //对所有List类型进行扩展print函数 |
可空扩展
在Java中用一个空对象去调用成员方法是会引发NPE异常的,但是Kotlin
中支持可空扩展,即允许null
调用并返回值。
1 | //当用一个空对象去调用时将返回“Null Object”。 |
扩展函数是静态分发的
由上面的例子我们可以看到,当我们给View
增加扩展函数之后,其所有的子类均能使用print
方法。那么如果某一个子类本身有print
方法那么会调用哪一个呢?
1 | //扩展函数是静态分发证实 |
1、对于非空对象,当扩展函数与类本身的成员函数(无论是子类成员函数还是父类成员函数)冲突时始终调用类本身成员函数。 2、对于可空扩展,当对象为空时调用扩展函数,当对象非空时遵循第一条。
3、当子类的扩展函数与父类扩展函数一致时,调用子类扩展函数。
实例分析
List的扩展函数filter
1 | /** |
看到这两个原型第一反应肯定是predicate
是什么玩意,其实predicate
类似于Java
中的接口,只不过这里声明比较简洁,predicate: (T) -> Boolean
表示predicate
是一个Function
,这个Function
输入一个类型为T
的参数返回一个Boolean
,如果有使用RxJava
的话,对这个会非常熟悉,其等价于RxJava
中的Function<T,Boolean>
。在Kotlin
中,可以将a.invoke()
简写成a()
,即此处的predicate(element)
等价于predicate.invoke(element)
,因此这个扩展函数的功能很容易理解,即将所有符合条件的元素装在一个新的集合里返回。
上面的函数还出现有一个关键字
inline
,该关键字表示这个函数在编译的时候会嵌入到调用的地方去。并且从上面的例子可以看出,一个扩展函数是可以调用另外一个扩展函数的。
利用中缀表达式进行函数复合
什么叫复合?简单一点讲就是把两个连续调用的函数组合起来,当然不仅仅是组合起来那么简单。直接看代码:
1 | //利用中缀表示法扩展函数 进行函数复合 |
上面这段代码可能更不容易理解。其实变量声明中的类型是可以省略的,如果省略的话代码将会更难理解。手写使用了
infix
关键字,这个关键字声明的函数表示允许使用a funName b
这样使用,其实等价于a.funName(b)
。Kotlin在简洁这个方向走得很远。显示这是一个把P1->P2->R
变换成P1->R
的过程。
操作符重载
Kotlin
还有一个Java
没有的特性,也就是操作符重载。有了操作符重载我们可以愉快的将两个对象加起来变成另外一个对象。
1 | class M(var i: Int) { |
这里的操作符重载其实是用一些列指定的函数来重定义操作符的功能,比如用
plus
来表示+
,用times
来表示×
。当你的类实现了或者扩展了这一类方法,则可以在使用时用操作符来代替。
Kotlin中表达式翻译表
表中左边为使用形态,右边为其执行形态。
表达式 | 翻译为 |
---|---|
a+b | a.plus(b) |
a-b | a.minus(b) |
a*b | a.times(b) |
a/b | a.div(b) |
a%b | a.rem(b) |
a..b | a.rangeTo(b) |
a in b | b.contains(a) |
a !in b | !b.contains(a) |
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] =b | a.set(i_1, ……, i_n,b) |
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i,j) | a.invoke(i, j) |
a(i_1,……,i_n) | a.invoke(i_1, ……,i_n) |
a +=b | a.plusAssign(b) |
a -=b | a.minusAssign(b) |
a *=b | a.timesAssign(b) |
a /=b | a.divAssign(b) |
a %=b | a.modAssign(b) |
a ==b | a?.equals(b) ?: (b === null) |
a !=b | !(a?.equals(b) ?: (b ===null)) |
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >=b | a.compareTo(b) >=0 |
a <=b | a.compareTo(b) <=0 |
总结
Kotlin
利用扩展函数实现了绝大部分标准库,让代码写得更简洁,但也因为处处缩写,也会造成可阅读性稍有降低,但只要理解了他的基本原理,就不难理解其是如何工作的。