扩展函数与属性
扩展 (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)