扩展函数与属性
扩展(Extensions)是 Kotlin 的一项“黑魔法”,它允许你在不继承类、不使用装饰器模式的情况下,为已有的类添加新功能。这对于增强第三方库(如 JDK、Android SDK)的 API 极其有用。
扩展函数 (Extension Functions)
要声明一个扩展函数,需要在函数名前加上接收者类型 (Receiver Type)。
kotlin
// 为 String 类添加 lastChar 功能
fun String.lastChar(): Char = this.get(this.length - 1)
// 调用
println("Kotlin".lastChar())- 接收者类型:
String(你要扩展谁?) - 接收者对象:
this(调用时那个具体的实例)
核心原理:静态分发 (Static Dispatch) 重要
扩展函数并不是真的修改了类文件,它也没有插入任何虚函数。扩展函数的调用是在编译时决定的,完全取决于变量的声明类型,而不是运行时的实际类型。
发生了什么?
编译器将扩展函数转换为了一个静态方法,将接收者作为第一个参数传入。
java
// 编译后的 Java 伪代码
public static char lastChar(String $this) {
return $this.charAt($this.length() - 1);
}多态失效演示
kotlin
open class Shape
class Rectangle : Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printName(s: Shape) {
// ⚠️ 哪怕传入的是 Rectangle,因为 s 声明为 Shape,调用的永远是 Shape.getName
println(s.getName())
}
printName(Rectangle()) // 输出: "Shape"成员优先原则
如果扩展函数与类成员函数签名完全一致(名称和参数都一样),成员函数永远优先。扩展函数就像是“备胎”,只有正主不在时才会被调用。
kotlin
class Example {
fun print() = println("Member")
}
fun Example.print() = println("Extension")
Example().print() // 输出: "Member"也可以重载
虽然不能覆盖,但你可以通过改变参数列表来重载同名函数,此时扩展函数是有效的。
扩展属性 (Extension Properties)
除了函数,你还可以扩展属性。但由于扩展不真正修改类,扩展属性不能有幕后字段 (Backing Field),也就是说它不能存储状态,只能提供 getter/setter。
kotlin
val <T> List<T>.lastIndex: Int
get() = size - 1 // 计算属性
// ❌ 错误:扩展属性不能初始化
// val String.foo = 1可空接收者扩展 (Nullable Receiver)
你可以为可空类型 T? 定义扩展。这允许你在扩展函数内部处理 null 情况,从而让调用者无需进行空安全调用 ?.。
kotlin
fun Any?.toStringOrEmpty(): String {
if (this == null) return ""
// 此时编译器已智能转换 this 为非空
return toString()
}
val s: String? = null
println(s.toStringOrEmpty()) // 输出 "",不会崩,也不用写 s?.作用域与导入
扩展函数通常定义在顶层。如果要在其他包中使用,必须显式 import。
kotlin
import com.example.utils.lastChar
"abc".lastChar()成员扩展 (Member Extensions)
你可以在一个类 ClassA 内部为 ClassB 定义扩展。
- 这种扩展只能在
ClassA的作用域内调用。 - 它同时拥有两个
this:ClassA(分发接收者)和ClassB(扩展接收者)。 - 这是构建 DSL 的基础结构(详见 DSL 章节)。
伴生对象扩展
如果一个类定义了伴生对象 (companion object),你可以为伴生对象定义扩展函数。这看起来就像是给类添加了“静态方法”。
kotlin
class MyClass {
companion object {}
}
fun MyClass.Companion.foo() {
...
}
// 调用
MyClass.foo()总结
- 本质:带接收者的静态方法。
- 分发:静态分发,看声明类型,不具备多态性。
- 存储:扩展属性不能存储状态。
- 安全:可空接收者扩展可以消灭部分
null检查。