Kotlin 2.1.0 入门教程(二十)扩展

扩展

Kotlin 提供了一种能力,无需继承类或使用像装饰器这样的设计模式,就能为类或接口扩展新的功能。这是通过一种名为扩展的特殊声明来实现的。

例如,你可以为无法修改的第三方库中的类或接口编写新的函数。这些函数可以像原类的方法一样以常规方式调用。这种机制被称为扩展函数。此外,还有扩展属性,它允许你为现有类定义新的属性。

扩展函数

要声明一个扩展函数,需要在函数名前加上接收者类型,该接收者类型指的是要被扩展的类型。以下代码为 MutableList<Int> 添加了一个 swap 函数:

kotlin 复制代码
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

扩展函数内部的 this 关键字对应接收者对象(即那个在点号之前传递的对象)。现在,你可以在任何 MutableList<Int> 上调用这样的函数:

kotlin 复制代码
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)

这个函数对于任何 MutableList<T> 都有意义,可以将它泛型化:

kotlin 复制代码
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

你需要在函数名之前声明泛型类型参数,以便它能在接收者类型表达式中使用。有关泛型的更多信息,请参阅泛型函数。

扩展是静态解析的

扩展实际上并不会修改它们所扩展的类。通过定义一个扩展,你并没有向类中插入新的成员,只是让新的函数可以通过点号表示法在该类型的变量上调用。

扩展函数是静态分发的。因此,调用哪个扩展函数在编译时就已经根据接收者类型确定了。例如:

kotlin 复制代码
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 类。

如果一个类有一个成员函数,同时又定义了一个扩展函数,且该扩展函数的接收者类型、名称都与成员函数相同,并且能应用于给定的参数,那么成员函数总是会被优先调用。例如:

kotlin 复制代码
class Example {
    fun printFunctionType() {
        println("Class method")
    }
}

fun Example.printFunctionType() {
    println("Extension function")
}

fun main() {
    Example().printFunctionType() // Class method
}

然而,扩展函数对同名但签名不同的成员函数进行重载是完全没问题的:

kotlin 复制代码
class Example {
    fun printFunctionType() {
        println("Class method")
    }
}

fun Example.printFunctionType(i: Int) {
    println("Extension function")
}

fun main() {
    Example().printFunctionType(1) // Extension function
}

可空接收者

请注意,扩展可以使用可空的接收者类型来定义。即使对象变量的值为 null,也可以在该变量上调用这些扩展函数。如果接收者为 null,那么 this 也为 null。因此,在定义具有可空接收者类型的扩展时,我们建议在函数体内部进行 this == null 检查,以避免编译错误。

Kotlin 中,你可以直接调用 toString() 方法而无需检查是否为 null,因为该检查已经在扩展函数内部完成了:

kotlin 复制代码
fun Any?.toString(): String {
    if (this == null) return "null"
    return toString()
}

扩展属性

Kotlin 对扩展属性的支持与对扩展函数的支持非常相似:

kotlin 复制代码
val <T> List<T>.lastIndex: Int
    get() = size - 1

由于扩展实际上并不会向类中插入成员,因此扩展属性无法高效地拥有幕后字段。这就是为什么扩展属性不允许使用初始化器的原因。扩展属性的行为只能通过显式提供 getter / setter 来定义。

示例:

kotlin 复制代码
// 错误:扩展属性不允许使用初始化器。
val House.number = 1

伴生对象扩展

如果一个类定义了伴生对象,你也可以为伴生对象定义扩展函数和扩展属性。

就像伴生对象的常规成员一样,调用它们时只需使用类名作为限定符:

kotlin 复制代码
class MyClass {
    // 该伴生对象将被称为 Companion。
    companion object { }
}

fun MyClass.Companion.printCompanion() {
    println("companion")
}

fun main() {
    MyClass.printCompanion()
}

扩展的作用域

在大多数情况下,你会在顶层直接在包下定义扩展:

kotlin 复制代码
package org.example.declarations

fun List<String>.getLongestString() { /*...*/ }

要在声明扩展的包之外使用该扩展,需在调用处导入它:

kotlin 复制代码
package org.example.usage

import org.example.declarations.getLongestString

fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

将扩展声明为成员

你可以在一个类内部为另一个类声明扩展。在这样的扩展内部,存在多个隐式接收者,这些对象的成员可以无需限定符即可访问。声明扩展的类的实例被称为分发接收者,而扩展方法的接收者类型的实例被称为扩展接收者。

kotlin 复制代码
class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }

    fun Host.printConnectionString() {
        // 调用 Host.printHostname()。
        printHostname()
        
        print(":")
        
        // 调用 Connection.printPort()。
        printPort()
    }

    fun connect() {
        // 调用扩展函数。
        host.printConnectionString()
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()

    // 错误,该扩展函数在 Connection 外部不可用。
    // Host("kotlin").printConnectionString()
}

如果分发接收者和扩展接收者的成员发生名称冲突,扩展接收者的成员优先。若要引用分发接收者的成员,你可以使用限定 this 语法。

kotlin 复制代码
class Connection {
    fun Host.getConnectionString() {
        // 调用 Host.toString()。
        toString()

        // 调用 Connection.toString()。
        this@Connection.toString()
    }
}

作为成员声明的扩展可以被声明为 open 并在子类中重写。这意味着此类函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。

kotlin 复制代码
open class Base

class Derived : Base()

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()
    }
}

class DerivedCaller : BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}

fun main() {
    BaseCaller().call(Base()) // Base extension function in BaseCaller
    BaseCaller().call(Derived()) // Base extension function in BaseCaller
    DerivedCaller().call(Base()) // Base extension function in DerivedCaller
    DerivedCaller().call(Derived()) // Base extension function in DerivedCaller
}

输出结果及原因分析

BaseCaller().call(Base()):输出 Base extension function in BaseCaller

  • BaseCaller 类的 call 方法中,参数 b 的类型是 Base

  • 当调用 b.printFunctionInfo() 时,由于 bBase 类型,会调用 BaseCaller 类中为 Base 类定义的扩展函数 Base.printFunctionInfo(),所以输出 Base extension function in BaseCaller

BaseCaller().call(Derived()):输出 Base extension function in BaseCaller

  • 虽然传递给 call 方法的实际对象是 Derived 类型,但 call 方法的参数类型声明为 Base

  • 扩展函数是静态解析的,也就是说,调用哪个扩展函数是根据参数的声明类型来决定的,而不是实际类型。因此,这里仍然会调用 BaseCaller 类中为 Base 类定义的扩展函数 Base.printFunctionInfo(),输出 Base extension function in BaseCaller

DerivedCaller().call(Base()):输出 Base extension function in DerivedCaller

  • DerivedCaller 继承自 BaseCaller,并且重写了 Base 类的扩展函数 Base.printFunctionInfo()

  • 当调用 DerivedCaller().call(Base()) 时,call 方法在 BaseCaller 类中定义,但是在 DerivedCaller 实例上调用,由于 DerivedCaller 重写了 Base 类的扩展函数,所以会调用重写后的扩展函数,输出 Base extension function in DerivedCaller

DerivedCaller().call(Derived()):输出 Base extension function in DerivedCaller

  • 同样,call 方法的参数类型声明为 Base,扩展函数是静态解析的,根据参数的声明类型来决定调用哪个扩展函数。

  • 因为是在 DerivedCaller 实例上调用 call 方法,而 DerivedCaller 重写了 Base 类的扩展函数,所以会调用重写后的 Base 类的扩展函数,输出 Base extension function in DerivedCaller

可见性说明

扩展使用的可见性修饰符,与在相同作用域中声明的普通函数所用的可见性修饰符相同。例如:

  • 在文件顶层声明的扩展,可以访问同一文件中的其他 private 顶层声明。

  • 如果在接收者类型之外声明扩展,它无法访问接收者的 privateprotected 成员。

相关推荐
阿巴斯甜11 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker12 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952713 小时前
Andorid Google 登录接入文档
android
黄林晴14 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android