Kotlin 的扩展(Extension),主要分为两种语法:第一个是扩展函数,第二个是扩展属性。
关键点 接收者.函数名
一、扩展函数
在类的外部为它定义一个新的成员方法
举例,以String类作为接收者,通过 Kotlin 的扩展特性,为它新增一个 lastElement() 方法。
// Ext.kt
package com.boycoder.chapter06
/*
① ② ③ ④
↓ ↓ ↓ ↓ */
fun String.lastElement(): Char? {
// ⑤
// ↓
if (this.isEmpty()) {
return null
}
return this[length - 1]
}
// 使用扩展函数
fun main() {
val msg = "Hello Wolrd"
// lastElement就像String的成员方法一样可以直接调用
val last = msg.lastElement() // last = d
}
这个扩展函数是直接定义在 Kotlin 文件里的,而不是定义在某个类当中的。这种扩展函数,我们称之为“顶层扩展”,这么叫它是因为它并没有嵌套在任何的类当中,它自身就在最外层。
注释①,fun关键字,代表我们要定义一个函数。也就是说,不管是定义普通 Kotlin 函数,还是定义扩展函数,我们都需要 fun 关键字。
注释②,“String.”,代表我们的扩展函数是为 String 这个类定义的。在 Kotlin 当中,它有一个名字,叫做接收者(Receiver),也就是扩展函数的接收方。
注释③,lastElement(),是我们定义的扩展函数的名称。
注释④,“Char?”,代表扩展函数的返回值是可能为空的 Char 类型。
注释⑤,“this.”,代表“具体的 String 对象”,当我们调用 msg.lastElement() 的时候,this 就代表了 msg。
如果去掉注释②处的“String.”,这段代码就会变成一个普通的函数定义;相反,在普通函数的名称前面加上一个“接收者类型”,比如“String.”,Kotlin 的“普通函数”就变成了“扩展函数”。
原理:Kotlin 编写的扩展函数调用代码,最终会变成静态方法的调用。
二、扩展属性
在类的外部为它定义一个新的成员属性
举例,以String类作为接收者,通过 Kotlin 的扩展特性,为它新增一个 lastElement 成员属性。
// 接收者类型
// ↓
val String.lastElement: Char?
get() = if (isEmpty()) {
null
} else {
get(length - 1)
}
fun main() {
val msg = "Hello Wolrd"
// lastElement就像String的成员属性一样可以直接调用
val last = msg.lastElement // last = d
}
Kotlin 的扩展表面上看起来是为一个类扩展了新的成员,但是本质上,它还是静态方法。而且,不管是扩展函数还是扩展属性,它本质上都会变成一个静态的方法。
什么时候该用扩展函数,什么时候该用扩展属性呢?其实,我们只需要看扩展在语义上更适合作为函数还是属性就够了。比如这里的 lastElement,它更适合作为一个扩展属性。这样设计的话,在语义上,lastElement 就像是 String 类当中的属性一样,它代表了字符串里的最后一个字符。
三、扩展的能力边界
Kotlin 扩展的应用范围还是非常广的。它最主要的用途,就是用来取代 Java 当中的各种工具类,比如 StringUtils、DateUtils 等等。
所有 Java 工具类能做的事情,Kotlin 扩展函数都可以做,并且可以做得更好。扩展函数的优势在于,开发工具可以在编写代码的时候智能提示。
当我们想要从外部为一个类扩展一些方法和属性的时候,我们就可以通过扩展来实现了。在 Kotlin 当中,几乎所有的类都可以被扩展,包括普通类、单例类、密封类、枚举类、伴生对象,甚至还包括第三方提供的 Java 类。唯有匿名内部类,由于它本身不存在名称,我们无法指定“接收者类型”,所以不能被扩展,当然了,它也没必要被扩展。
扩展不能做什么?
第一个限制,Kotlin 扩展不是真正的类成员,因此它无法被它的子类重写。
第二个限制,扩展属性无法存储状态。背后的根本原因,还是因为它们都是静态方法。
第三个限制,扩展的访问作用域仅限于两个地方。第一,定义处的成员;第二,接收者类型的公开成员。
如果 访问作用域 是 接收者 的 private、protected 成员,那我们将无法在扩展函数当中访问它。归根结底,还是因为扩展函数并非真正的类成员。
针对扩展的第三个限制来说: 如果扩展是顶层的扩展,那么扩展的访问域仅限于该 Kotlin 文件当中的所有成员,以及被扩展类型的公开成员,这种方式定义的扩展是可以被全局使用的。 如果扩展是被定义在某个类当中的,那么该扩展的访问域仅限于该类当中的所有成员,以及被扩展类型的公开成员,这种方式定义的扩展仅能在该类当中使用。
四、Kotlin 扩展两个核心使用场景
主动使用扩展,通过它来优化软件架构。
对复杂的类进行职责划分,关注点分离。让类的核心尽量简单易懂,而让类的功能性属性与方法以扩展的形式存在于类的外部。比如源码的String.kt与Strings.kt。
被动使用扩展,提升可读性与开发效率,减少模版代码。
当我们无法修改外部的 SDK 时,对于重复的代码模式,我们将其以扩展的方式封装起来,提供给对应的接收者类型。
小结:
Kotlin 的扩展,从语法角度来看,分为扩展函数和扩展属性。定义扩展的方式,只是比普通函数、属性多了一个“扩展接收者”而已。
从作用域角度来看,分为顶层扩展和类内扩展。
从本质上来看,扩展函数和扩展属性,它们都是 Java 静态方法,与 Java 当中的工具类别无二致。对比 Java 工具类,扩展最大的优势就在于,IDE 可以为我们提供代码补全功能。
从能力的角度来看,Kotlin 扩展一共有三个限制,分别是:扩展无法被重写;扩展属性无法存储状态;扩展的作用域有限,无法访问私有成员。
从使用场景的角度来看,Kotlin 扩展主要有两个使用场景,分别是:关注点分离,优化代码架构;消灭模板代码,提高可读性和开发效率。