Skip to content

扩展函数与属性

源:Kotlin 扩展官方文档

扩展 (Extensions) 是 Kotlin 抛弃 "Utils 类" 的核心手段。它允许你为任何类(包括第三方 SDK 的类)添加新功能,看起来就像是该类原本的成员一样。

基础语法

kotlin
// 为 Context 类扩展一个 toast 方法
// "Context" 是接收者类型 (Receiver Type)
// "this" 指代调用该方法的对象实例
fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

// 调用
activity.toast("Hello World")

可空接收者 (Nullable Receiver)

你可以为可空类型定义扩展,统一处理 null 情况。

kotlin
// 为 Any? 定义扩展
fun Any?.toStringOrEmpty(): String {
    if (this == null) return ""
    return toString()
}

val s: String? = null
println(s.toStringOrEmpty()) // 输出 "",不会崩

核心原理:静态解析 (Static Resolution)

极其重要

扩展函数 不是 虚函数。它们是 静态分发 的。 这意味着:具体调用哪个扩展函数,完全取决于编译时参数的声明类型,而不是运行时的实际类型。

kotlin
open class Shape
class Rectangle : Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printName(s: Shape) {
    // 参数 s 声明为 Shape,所以永远调用 Shape.getName()
    // 即使运行时传入的是 Rectangle 实例!
    println(s.getName())
}

printName(Rectangle()) // 输出 "Shape"

这与成员函数的多态行为截然不同。如果成员函数和扩展函数签名相同,成员函数总是优先

扩展属性

扩展属性实际上是提供了自定义的 Getter/Setter,它不能有幕后字段 (Backing Field)。

kotlin
// 为 List 添加 lastIndex 属性
val <T> List<T>.lastIndex: Int
    get() = size - 1

val list = listOf("A", "B")
println(list.lastIndex) // 1

类成员扩展 (Member Extensions)

你可以在一个类内部为另一个类定义扩展。 这种写法常用于 DSL 构建,因为它可以同时访问两个接收者的上下文。

  • 分发接收者 (Dispatch Receiver): 扩展函数所在的类实例 (Host)。
  • 扩展接收者 (Extension Receiver): 被扩展的类实例 (Connection)。
kotlin
class Host(val hostName: String) {
    fun Connection.printStatus() {
        // 同时访问 hostName (分发接收者) 和 id (扩展接收者)
        print("Connection ${this.id} on $hostName")
    }

    fun connect(c: Connection) {
        c.printStatus() // 仅在 Host 类内部可用
    }
}

伴生对象扩展

如果你想为某个类添加类似 "静态方法" 的扩展,需将其定义在伴生对象上。

kotlin
class MyClass {
    companion object
}

// 扩展伴生对象
fun MyClass.Companion.create(): MyClass = MyClass()

// 调用
MyClass.create()

常用 KTX 案例

Android Jetpack 的 KTX 库本质上就是一系列实用的扩展函数。

kotlin
// View.isVisible
// 替代 if (show) view.visibility = View.VISIBLE else View.GONE
var View.isVisible: Boolean
    get() = visibility == View.VISIBLE
    set(value) {
        visibility = if (value) View.VISIBLE else View.GONE
    }
    
// String.toUri
inline fun String.toUri(): Uri = Uri.parse(this)