扩展是kotlin
提供了一种在不必继承自类或使用装饰器等设计模式的情况下,为类或接口添加新功能的能力。
例如,你可以为无法修改的来自第三方库的类或接口编写新函数。这些函数可以像原始类的方法一样被通常调用。这种机制称为扩展方法。还有扩展属性,允许你为现有的类定义新的属性
要声明扩展方法,需要方法名称前加上要扩展的类。
为MutableList
类添加了一个swap
方法
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
fun main() {
val mutableList = MutableList(4) { it }
println(mutableList) // [0, 1, 2, 3]
mutableList.swap(0,1)
println(mutableList) // [1, 0, 2, 3]
}
扩展函数内的 this 关键字对应于接收者对象(即在点号之前传递的对象)
如果想要到任意类型的MutableList
类生效,需要把
变为泛型
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
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 #$i") }
fun main() {
Example().printFunctionType(1) // Extension function #1
}
可以使用可空接收者类型来定义扩展。这些扩展可以在对象变量上调用,即使其值为null
。在使用可空接收者类型定义扩展时,建议在函数体内执行this == null
检查,以避免编译器错误
原文作者
在kotlin
中,可以在不检查null
的情况下调用toString()
,因为该检查已经在扩展函数内部进行了
fun Any?.toString(): String {
if (this == null) return "null"
//在null检查之后,'this'被自动转换为非空类型,因此下面的toString()
//解析为Any类的成员函数
return toString()
}
kotlin
像支持函数一样支持扩展属性
class Example() {
var inx = 1
}
// val Example.number = 1 // 报错 -- error: initializers are not allowed for extension properties
var Example.lastIndex: Int
get() = inx - 1
set(value) {
inx = value
}
fun main() {
println(Example().lastIndex ) // 0
}
注意,扩展属性不会被插进类中,不能存储值,不会提供默认的访问器,实际是为现有类型提供了额外的访问器
如果一个类定义了伴生对象,你也可以为伴生对象定义扩展函数和属性。与伴生对象的常规成员一样,它们可以仅使用类名作为限定符来调用
class MyClass {
companion object { } // 伴生对象
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion() // companion
}
类中对象声明使用
companion
关键字,详解见后边文章
正常情况下载顶层进行扩展,即在包下
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() {
printHostname() // 调用 Host.printHostname()
print(":")
printPort() // 调用 Connection.printPort()
}
fun connect() {
host.printConnectionString() // 调用扩展函数
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect() // kotl.in:443
}
如果分发接收者和扩展接收者的成员之间发生名称冲突,则扩展接收者优先。要引用分发接收者的成员,可以使用限定this
语法
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection {
fun Host.getConnectionString() {
toString() // 调用 Host.toString()
this@Connection.toString() // 调用 Connection.toString()
}
}
扩展成员可以声明为open
类型,并在子类中被重写。这意味着对于分发接收者类型而言,这些函数的分发是虚拟的 1,但对于扩展接收者类型而言是静态的。
open class Base { }
class Derived : Base() { }
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("BaseCaller Base")
}
open fun Derived.printFunctionInfo() {
println("BaseCaller Derived")
}
fun call(b: Base) {
b.printFunctionInfo()
}
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() {
println("DerivedCaller Base")
}
override fun Derived.printFunctionInfo() {
println("DerivedCaller Derived")
}
}
fun main() {
BaseCaller().call(Base()) // BaseCaller Base
DerivedCaller().call(Base()) // DerivedCaller Base
DerivedCaller().call(Derived()) // DerivedCaller Base
}
所有的输出都是" * Base "
扩展使用与在相同范围声明的常规函数相同的可见性修饰符。如
在文件的顶层声明的扩展可以访问该文件中的其他私有顶层声明。
如果扩展在其接收者类型之外声明,它将无法访问接收者的私有或受保护成员。
如果在子类中重写了这个扩展函数,那么在运行时将根据对象的实际类型来调用相应的函数,这是虚拟分发的概念 ↩︎