Kotlin 2.1.0 入门教程(二十)扩展
扩展
Kotlin
提供了一种能力,无需继承类或使用像装饰器这样的设计模式,就能为类或接口扩展新的功能。这是通过一种名为扩展的特殊声明来实现的。
例如,你可以为无法修改的第三方库中的类或接口编写新的函数。这些函数可以像原类的方法一样以常规方式调用。这种机制被称为扩展函数。此外,还有扩展属性,它允许你为现有类定义新的属性。
扩展函数
要声明一个扩展函数,需要在函数名前加上接收者类型,该接收者类型指的是要被扩展的类型。以下代码为 MutableList<Int>
添加了一个 swap
函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
扩展函数内部的 this
关键字对应接收者对象(即那个在点号之前传递的对象)。现在,你可以在任何 MutableList<Int>
上调用这样的函数:
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
这个函数对于任何 MutableList<T>
都有意义,可以将它泛型化:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
你需要在函数名之前声明泛型类型参数,以便它能在接收者类型表达式中使用。有关泛型的更多信息,请参阅泛型函数。
扩展是静态解析的
扩展实际上并不会修改它们所扩展的类。通过定义一个扩展,你并没有向类中插入新的成员,只是让新的函数可以通过点号表示法在该类型的变量上调用。
扩展函数是静态分发的。因此,调用哪个扩展函数在编译时就已经根据接收者类型确定了。例如:
open class Shape
class Rectangle : Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
fun main() {
printClassName(Rectangle()) // Shape
}
这个示例会打印出 Shape
,因为所调用的扩展函数仅取决于参数 s
的声明类型,该声明类型为 Shape
类。
如果一个类有一个成员函数,同时又定义了一个扩展函数,且该扩展函数的接收者类型、名称都与成员函数相同,并且能应用于给定的参数,那么成员函数总是会被优先调用。例如:
class Example {
fun printFunctionType() {
println("Class method")
}
}
fun Example.printFunctionType() {
println("Extension function")
}
fun main() {
Example().printFunctionType() // Class method
}
然而,扩展函数对同名但签名不同的成员函数进行重载是完全没问题的:
class Example {
fun printFunctionType() {
println("Class method")
}
}
fun Example.printFunctionType(i: Int) {
println("Extension function")
}
fun main() {
Example().printFunctionType(1) // Extension function
}
可空接收者
请注意,扩展可以使用可空的接收者类型来定义。即使对象变量的值为 null
,也可以在该变量上调用这些扩展函数。如果接收者为 null
,那么 this
也为 null
。因此,在定义具有可空接收者类型的扩展时,我们建议在函数体内部进行 this == null
检查,以避免编译错误。
在 Kotlin
中,你可以直接调用 toString()
方法而无需检查是否为 null
,因为该检查已经在扩展函数内部完成了:
fun Any?.toString(): String {
if (this == null) return "null"
return toString()
}
扩展属性
Kotlin
对扩展属性的支持与对扩展函数的支持非常相似:
val <T> List<T>.lastIndex: Int
get() = size - 1
由于扩展实际上并不会向类中插入成员,因此扩展属性无法高效地拥有幕后字段。这就是为什么扩展属性不允许使用初始化器的原因。扩展属性的行为只能通过显式提供 getter / setter
来定义。
示例:
// 错误:扩展属性不允许使用初始化器。
val House.number = 1
伴生对象扩展
如果一个类定义了伴生对象,你也可以为伴生对象定义扩展函数和扩展属性。
就像伴生对象的常规成员一样,调用它们时只需使用类名作为限定符:
class MyClass {
// 该伴生对象将被称为 Companion。
companion object { }
}
fun MyClass.Companion.printCompanion() {
println("companion")
}
fun main() {
MyClass.printCompanion()
}
扩展的作用域
在大多数情况下,你会在顶层直接在包下定义扩展:
package org.example.declarations
fun List<String>.getLongestString() { /*...*/ }
要在声明扩展的包之外使用该扩展,需在调用处导入它:
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
将扩展声明为成员
你可以在一个类内部为另一个类声明扩展。在这样的扩展内部,存在多个隐式接收者,这些对象的成员可以无需限定符即可访问。声明扩展的类的实例被称为分发接收者,而扩展方法的接收者类型的实例被称为扩展接收者。
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
// 调用 Host.printHostname()。
printHostname()
print(":")
// 调用 Connection.printPort()。
printPort()
}
fun connect() {
// 调用扩展函数。
host.printConnectionString()
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
// 错误,该扩展函数在 Connection 外部不可用。
// Host("kotlin").printConnectionString()
}
如果分发接收者和扩展接收者的成员发生名称冲突,扩展接收者的成员优先。若要引用分发接收者的成员,你可以使用限定 this
语法。
class Connection {
fun Host.getConnectionString() {
// 调用 Host.toString()。
toString()
// 调用 Connection.toString()。
this@Connection.toString()
}
}
作为成员声明的扩展可以被声明为 open
并在子类中重写。这意味着此类函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
open class Base
class Derived : Base()
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("Base extension function in BaseCaller")
}
open fun Derived.printFunctionInfo() {
println("Derived extension function in BaseCaller")
}
fun call(b: Base) {
b.printFunctionInfo()
}
}
class DerivedCaller : BaseCaller() {
override fun Base.printFunctionInfo() {
println("Base extension function in DerivedCaller")
}
override fun Derived.printFunctionInfo() {
println("Derived extension function in DerivedCaller")
}
}
fun main() {
BaseCaller().call(Base()) // Base extension function in BaseCaller
BaseCaller().call(Derived()) // Base extension function in BaseCaller
DerivedCaller().call(Base()) // Base extension function in DerivedCaller
DerivedCaller().call(Derived()) // Base extension function in DerivedCaller
}
输出结果及原因分析
BaseCaller().call(Base())
:输出 Base extension function in BaseCaller
。
-
在
BaseCaller
类的call
方法中,参数b
的类型是Base
。 -
当调用
b.printFunctionInfo()
时,由于b
是Base
类型,会调用BaseCaller
类中为Base
类定义的扩展函数Base.printFunctionInfo()
,所以输出Base extension function in BaseCaller
。
BaseCaller().call(Derived())
:输出 Base extension function in BaseCaller
。
-
虽然传递给
call
方法的实际对象是Derived
类型,但call
方法的参数类型声明为Base
。 -
扩展函数是静态解析的,也就是说,调用哪个扩展函数是根据参数的声明类型来决定的,而不是实际类型。因此,这里仍然会调用
BaseCaller
类中为Base
类定义的扩展函数Base.printFunctionInfo()
,输出Base extension function in BaseCaller
。
DerivedCaller().call(Base())
:输出 Base extension function in DerivedCaller
。
-
DerivedCaller
继承自BaseCaller
,并且重写了Base
类的扩展函数Base.printFunctionInfo()
。 -
当调用
DerivedCaller().call(Base())
时,call
方法在BaseCaller
类中定义,但是在DerivedCaller
实例上调用,由于DerivedCaller
重写了Base
类的扩展函数,所以会调用重写后的扩展函数,输出Base extension function in DerivedCaller
。
DerivedCaller().call(Derived())
:输出 Base extension function in DerivedCaller
。
-
同样,
call
方法的参数类型声明为Base
,扩展函数是静态解析的,根据参数的声明类型来决定调用哪个扩展函数。 -
因为是在
DerivedCaller
实例上调用call
方法,而DerivedCaller
重写了Base
类的扩展函数,所以会调用重写后的Base
类的扩展函数,输出Base extension function in DerivedCaller
。
可见性说明
扩展使用的可见性修饰符,与在相同作用域中声明的普通函数所用的可见性修饰符相同。例如:
-
在文件顶层声明的扩展,可以访问同一文件中的其他
private
顶层声明。 -
如果在接收者类型之外声明扩展,它无法访问接收者的
private
或protected
成员。