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

打印 上一主题 下一主题

主题 1014|帖子 1014|积分 3042

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
扩展

Kotlin 提供了一种能力,无需继续类或使用像装饰器这样的设计模式,就能为类或接口扩展新的功能。这是通过一种名为扩展的特殊声明来实现的。
例如,你可以为无法修改的第三方库中的类或接口编写新的函数。这些函数可以像原类的方法一样以常规方式调用。这种机制被称为扩展函数。此外,还有扩展属性,它答应你为现有类界说新的属性。
扩展函数

要声明一个扩展函数,需要在函数名前加上汲取者类型,该汲取者类型指的是要被扩展的类型。以下代码为 MutableList<Int> 添加了一个 swap 函数:
  1. fun MutableList<Int>.swap(index1: Int, index2: Int) {
  2.     val tmp = this[index1]
  3.     this[index1] = this[index2]
  4.     this[index2] = tmp
  5. }
复制代码
扩展函数内部的 this 关键字对应汲取者对象(即那个在点号之前传递的对象)。如今,你可以在任何 MutableList<Int> 上调用这样的函数:
  1. val list = mutableListOf(1, 2, 3)
  2. list.swap(0, 2)
复制代码
这个函数对于任何 MutableList<T> 都有意义,可以将它泛型化:
  1. fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
  2.     val tmp = this[index1]
  3.     this[index1] = this[index2]
  4.     this[index2] = tmp
  5. }
复制代码
你需要在函数名之前声明泛型类型参数,以便它能在汲取者类型表达式中使用。有关泛型的更多信息,请参阅泛型函数。
扩展是静态解析的

扩展实际上并不会修改它们所扩展的类。通过界说一个扩展,你并没有向类中插入新的成员,只是让新的函数可以通过点号表示法在该类型的变量上调用。
扩展函数是静态分发的。因此,调用哪个扩展函数在编译时就已经根据汲取者类型确定了。例如:
  1. open class Shape
  2. class Rectangle : Shape()
  3. fun Shape.getName() = "Shape"
  4. fun Rectangle.getName() = "Rectangle"
  5. fun printClassName(s: Shape) {
  6.     println(s.getName())
  7. }
  8. fun main() {
  9.     printClassName(Rectangle()) // Shape
  10. }
复制代码
这个示例会打印出 Shape,由于所调用的扩展函数仅取决于参数 s 的声明类型,该声明类型为 Shape 类。
如果一个类有一个成员函数,同时又界说了一个扩展函数,且该扩展函数的汲取者类型、名称都与成员函数类似,并且能应用于给定的参数,那么成员函数总是会被优先调用。例如:
  1. class Example {
  2.     fun printFunctionType() {
  3.         println("Class method")
  4.     }
  5. }
  6. fun Example.printFunctionType() {
  7.     println("Extension function")
  8. }
  9. fun main() {
  10.     Example().printFunctionType() // Class method
  11. }
复制代码
然而,扩展函数对同名但签名差别的成员函数举行重载是完全没问题的:
  1. class Example {
  2.     fun printFunctionType() {
  3.         println("Class method")
  4.     }
  5. }
  6. fun Example.printFunctionType(i: Int) {
  7.     println("Extension function")
  8. }
  9. fun main() {
  10.     Example().printFunctionType(1) // Extension function
  11. }
复制代码
可空汲取者

请注意,扩展可以使用可空的汲取者类型来界说。纵然对象变量的值为 null,也可以在该变量上调用这些扩展函数。如果汲取者为 null,那么 this 也为 null。因此,在界说具有可空汲取者类型的扩展时,我们发起在函数体内部举行 this == null 检查,以避免编译错误。
在 Kotlin 中,你可以直接调用 toString() 方法而无需检查是否为 null,由于该检查已经在扩展函数内部完成了:
  1. fun Any?.toString(): String {
  2.     if (this == null) return "null"
  3.     return toString()
  4. }
复制代码
扩展属性

Kotlin 对扩展属性的支持与对扩展函数的支持非常相似:
  1. val <T> List<T>.lastIndex: Int
  2.     get() = size - 1
复制代码
由于扩展实际上并不会向类中插入成员,因此扩展属性无法高效地拥有幕后字段。这就是为什么扩展属性不答应使用初始化器的缘故原由。扩展属性的举动只能通过显式提供 getter / setter 来界说。
示例:
  1. // 错误:扩展属性不允许使用初始化器。
  2. val House.number = 1
复制代码
伴生对象扩展

如果一个类界说了伴生对象,你也可以为伴生对象界说扩展函数和扩展属性。
就像伴生对象的常规成员一样,调用它们时只需使用类名作为限定符:
  1. class MyClass {
  2.     // 该伴生对象将被称为 Companion。
  3.     companion object { }
  4. }
  5. fun MyClass.Companion.printCompanion() {
  6.     println("companion")
  7. }
  8. fun main() {
  9.     MyClass.printCompanion()
  10. }
复制代码
扩展的作用域

在大多数环境下,你会在顶层直接在包下界说扩展:
  1. package org.example.declarations
  2. fun List<String>.getLongestString() { /*...*/ }
复制代码
要在声明扩展的包之外使用该扩展,需在调用处导入它:
  1. package org.example.usage
  2. import org.example.declarations.getLongestString
  3. fun main() {
  4.     val list = listOf("red", "green", "blue")
  5.     list.getLongestString()
  6. }
复制代码
将扩展声明为成员

你可以在一个类内部为另一个类声明扩展。在这样的扩展内部,存在多个隐式汲取者,这些对象的成员可以无需限定符即可访问。声明扩展的类的实例被称为分发汲取者,而扩展方法的汲取者类型的实例被称为扩展汲取者。
  1. class Host(val hostname: String) {
  2.     fun printHostname() { print(hostname) }
  3. }
  4. class Connection(val host: Host, val port: Int) {
  5.     fun printPort() { print(port) }
  6.     fun Host.printConnectionString() {
  7.         // 调用 Host.printHostname()。
  8.         printHostname()
  9.         
  10.         print(":")
  11.         
  12.         // 调用 Connection.printPort()。
  13.         printPort()
  14.     }
  15.     fun connect() {
  16.         // 调用扩展函数。
  17.         host.printConnectionString()
  18.     }
  19. }
  20. fun main() {
  21.     Connection(Host("kotl.in"), 443).connect()
  22.     // 错误,该扩展函数在 Connection 外部不可用。
  23.     // Host("kotlin").printConnectionString()
  24. }
复制代码
如果分发汲取者和扩展汲取者的成员发生名称冲突,扩展汲取者的成员优先。若要引用分发汲取者的成员,你可以使用限定 this 语法。
  1. class Connection {
  2.     fun Host.getConnectionString() {
  3.         // 调用 Host.toString()。
  4.         toString()
  5.         // 调用 Connection.toString()。
  6.         this@Connection.toString()
  7.     }
  8. }
复制代码
作为成员声明的扩展可以被声明为 open 并在子类中重写。这意味着此类函数的分发对于分发汲取者类型是假造的,但对于扩展汲取者类型是静态的。
  1. open class Base
  2. class Derived : Base()
  3. open class BaseCaller {
  4.     open fun Base.printFunctionInfo() {
  5.         println("Base extension function in BaseCaller")
  6.     }
  7.     open fun Derived.printFunctionInfo() {
  8.         println("Derived extension function in BaseCaller")
  9.     }
  10.     fun call(b: Base) {
  11.         b.printFunctionInfo()
  12.     }
  13. }
  14. class DerivedCaller : BaseCaller() {
  15.     override fun Base.printFunctionInfo() {
  16.         println("Base extension function in DerivedCaller")
  17.     }
  18.     override fun Derived.printFunctionInfo() {
  19.         println("Derived extension function in DerivedCaller")
  20.     }
  21. }
  22. fun main() {
  23.     BaseCaller().call(Base()) // Base extension function in BaseCaller
  24.     BaseCaller().call(Derived()) // Base extension function in BaseCaller
  25.     DerivedCaller().call(Base()) // Base extension function in DerivedCaller
  26.     DerivedCaller().call(Derived()) // Base extension function in DerivedCaller
  27. }
复制代码
输出结果及缘故原由分析

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


  • 在 BaseCaller 类的 call 方法中,参数 b 的类型是 Base。
  • 当调用 b.printFunctionInfo() 时,由于 b 是 Base 类型,会调用 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 顶层声明。
  • 如果在汲取者类型之外声明扩展,它无法访问汲取者的 private 或 protected 成员。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表