17:kotlin 类和对象 -- 扩展(Extensions )

扩展是kotlin提供了一种在不必继承自类或使用装饰器等设计模式的情况下,为类或接口添加新功能的能力。

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

扩展方法

要声明扩展方法,需要方法名称前加上要扩展的类。

MutableList类添加了一个swap方法

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

fun main() {
    val mutableList = MutableList(4) { it }
    println(mutableList)  // [0, 1, 2, 3]
    mutableList.swap(0,1)
    println(mutableList)  // [1, 0, 2, 3]
}

扩展函数内的 this 关键字对应于接收者对象(即在点号之前传递的对象)

如果想要到任意类型的MutableList类生效,需要把变为泛型

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

扩展是静态解析的

扩展实际上并不修改它们所扩展的类,并没有将新成员插入到类中,只是使新函数可以使用该类型变量上的点符号进行调用。
扩展函数是静态分派的。因此,根据接收器类型,在编译时已经知道调用哪个扩展函数。

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,这一点与类成员方法不同

如果定义了一个和类成员方法完全一样(相同名称和参数类型)的扩展方法,则扩展方法不生效

class Example {
    fun printFunctionType() { println("Class method") }
}

// 编译器会提示 - 扩展方法被类成员方法屏蔽
fun Example.printFunctionType() { println("Extension function") }

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

如果只是方法名相同,参数类型不同则互不影响

class Example {
    fun printFunctionType() { println("Class method") }
}

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

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

可空接收者类型

可以使用可空接收者类型来定义扩展。这些扩展可以在对象变量上调用,即使其值为null。在使用可空接收者类型定义扩展时,建议在函数体内执行this == null检查,以避免编译器错误
原文作者
kotlin中,可以在不检查null的情况下调用toString(),因为该检查已经在扩展函数内部进行了

fun Any?.toString(): String {
    if (this == null) return "null"
    //在null检查之后,'this'被自动转换为非空类型,因此下面的toString()
    //解析为Any类的成员函数
    return toString()
}

扩展属性

kotlin像支持函数一样支持扩展属性

class Example() {
    var inx = 1
}

// val Example.number = 1 // 报错 -- error: initializers are not allowed for extension properties

var Example.lastIndex: Int
    get() = inx - 1
    set(value) {
        inx = value
    }

fun main() {
    println(Example().lastIndex ) // 0
}

注意,扩展属性不会被插进类中,不能存储值,不会提供默认的访问器,实际是为现有类型提供了额外的访问器

扩展伴生对象

如果一个类定义了伴生对象,你也可以为伴生对象定义扩展函数和属性。与伴生对象的常规成员一样,它们可以仅使用类名作为限定符来调用

class MyClass {
    companion object { }  // 伴生对象
}

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

fun main() {
    MyClass.printCompanion()    // companion
}

类中对象声明使用companion 关键字,详解见后边文章

扩展范围

正常情况下载顶层进行扩展,即在包下

package org.example.declarations

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

如果在声明之外访问扩展方法需要导入

package org.example.usage

import org.example.declarations.getLongestString

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

注意看两段代码的包名

将扩展声明为成员(Declaring extensions as members)

在一个类的内部声明扩展时,存在多个隐式接收者,可以在不使用限定符的情况下使用隐式接受者的成员变量

  • 分发接收者(dispatch receiver):定义扩展成员的类实例
  • 扩展接收者(extension receiver):接收扩展方法的类实例
class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

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

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

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

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

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

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection {
    fun Host.getConnectionString() {
        toString()         // 调用 Host.toString()
        this@Connection.toString()  // 调用 Connection.toString()
    }
}

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

open class Base { }

class Derived : Base() { }

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

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

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

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

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

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

所有的输出都是" * Base "

可见性说明

扩展使用与在相同范围声明的常规函数相同的可见性修饰符。如

  • 在文件的顶层声明的扩展可以访问该文件中的其他私有顶层声明。

  • 如果扩展在其接收者类型之外声明,它将无法访问接收者的私有或受保护成员。


  1. 如果在子类中重写了这个扩展函数,那么在运行时将根据对象的实际类型来调用相应的函数,这是虚拟分发的概念 ↩︎

你可能感兴趣的:(kotlin教程,kotlin,开发语言,android)