Skip to content

扩展函数与属性

源:Extensions

扩展(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 的作用域内调用。
  • 它同时拥有两个 thisClassA(分发接收者)和 ClassB(扩展接收者)。
  • 这是构建 DSL 的基础结构(详见 DSL 章节)。

伴生对象扩展

如果一个类定义了伴生对象 (companion object),你可以为伴生对象定义扩展函数。这看起来就像是给类添加了“静态方法”。

kotlin
class MyClass {
    companion object {}
}

fun MyClass.Companion.foo() {
    ...
}

// 调用
MyClass.foo()

总结

  • 本质:带接收者的静态方法。
  • 分发:静态分发,看声明类型,不具备多态性。
  • 存储:扩展属性不能存储状态。
  • 安全:可空接收者扩展可以消灭部分 null 检查。