一、定义
Kotlin 在不修改类 / 不继承类的情况下,向一个类添加新函数或者新属性,更符合开闭原则。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
- 扩展属性:定义在类或者kotlin文件中,不允许定义在函数中;
- 扩展函数:扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:
fun receiverType.functionName(params){
body
}
//receiverType:表示函数的接收者,也就是函数扩展的对象
//functionName:扩展函数的名称
//params:扩展函数的参数,可以为NULL
扩展的本质:扩展函数是定义在类外部的静态函数,函数的第一个参数是接收者类型的对象。这意味着调用扩展时不会创建适配对象或者任何运行时的额外消耗。
二、扩展函数
1、扩展函数是静态解析的,并不是接收者类型的虚拟成员
在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:
open class C
class D: C()
fun C.foo() = "c" // 扩展函数 foo
fun D.foo() = "d" // 扩展函数 foo
fun printFoo(c: C) {
println(c.foo()) // 类型是 C 类
}
fun main(arg:Array<String>){
printFoo(D())
}
//实际输出c
2、若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
3、扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
三、扩展属性
扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。
val Foo.bar = 1 // 错误:扩展属性不能有初始化器
扩展属性只能被声明为 val
四、伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:
class MyClass {
companion object { } // 将被称为 "Companion"
}
fun MyClass.Companion.foo() {
println("伴随对象的扩展函数")
}
val MyClass.Companion.no: Int
get() = 10
伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。
对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:
(1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
(2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
(3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;
(4)类外扩展的伴随对象函数可以被伴随对象内的函数引用;
五、扩展的作用域
通常扩展函数或属性定义在顶级包下;
要使用所定义包之外的一个扩展, 通过import导入扩展的函数名进行使用;
六、扩展声明为成员
- 在一个类内部你可以为另一个类声明扩展。
- 在这个扩展中,有个多个隐含的接受者,其中扩展方法定义所在类的实例称为分发接受者,而扩展方法的目标类型的实例称为扩展接受者。
- 以成员的形式定义的扩展函数, 可以声明为 open , 而且可以在子类中覆盖. 也就是说, 在这类扩展函数的派 发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
fun main(args: Array<String>) {
C().caller(D()) // 输出 "D.foo in C"
C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析
C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析
}
//实例执行输出结果为:
//D.foo in C
//D.foo in C1
//D.foo in C
七、内置扩展函数
扩展函数是 Kotlin 用于简化一些代码的书写产生的,其中有 五个函数
- let
- with
- run
- apply
- also
1、let 函数
- 在函数块内可以通过 it 指代该对象。
- 返回值为函数块的最后一行或指定return表达式。
//一般写法
val result = "hello".let {
println(it.length)
1000
}
使用场景:
1、使用let函数处理需要针对一个可null的对象统一做判空处理;
2、又或者是需要去明确一个变量所处特定的作用域范围内可以使用;
2、with 函数
- 前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。
- 返回值为函数块的最后一行或指定return表达式。
//一般写法
var result = with(Person()) {
println(this.name + this.age)
1000
}
使用场景:
1、适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上
3、run 函数
- 实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回。
- 返回值为最后一行的值或者指定的return的表达式。
var result = person.run {
println("$name + $age")
1000
}
使用场景:
1、适用于let,with函数任何场景。
因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理
4、apply 函数
- 从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样
- 返回值是传入对象的本身
val person = Person().apply {
name = "liming"
age = 50
}
使用场景:
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。正是基于这一点差异它的适用场景稍微与run函数有点不一样。
apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。特别是在我们开发中会有一些数据model向View model转化实例化的过程中需要用到
5、also 函数
- also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。
- 返回值是传入对象的本身。
val result = "hello".also {
println(it.length)
}
使用场景:
适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用。
区别
方法名 | 是否有it | 是否有this | return类型 |
---|---|---|---|
let | ✔ | × | 最后一句 |
also | ✔ | × | this |
apply | × | ✔ | this |
run | × | ✔ | 最后一句 |
with | × | ✔ | 最后一句 |
为什么run、with、apply用this,also 和 let 用it?
- run、with、apply 函数中的参数 block 是 「T 的扩展函数」,所以采用 this 是扩展函数的接收者对象(receiver)。另外因为 block 没有参数,所以不存在 it 的定义。
- also 和 let 参数 block 是 「参数为 T 的函数」,所以采用 it 是唯一参数(argument)。另外因为 block 不是扩展函数,所以不存在 this 的定义。