首页 Kotlin学习笔记(8) - 扩展
文章
取消

Kotlin学习笔记(8) - 扩展

一、扩展函数

Kotlin中可以对一个类或接口扩展新功能而无需继承或类似Decorator对设计模式,例如我们希望对一些三方库中的类或接口添加新方法,调用方式可以与原始方法一致。

定义时用适用被扩展类型为前缀加扩展的方法名即可,函数内部this指扩展类

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

也可以直接使用泛型,将对任何MutableList<T>类型生效

1
2
3
4
5
6
7
8
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
}

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'

通常还可以定义为可空接收,内部通过this == null进行校验

1
2
3
4
5
6
fun Any?.toString(): String {
    if (this == null) return "null"
    // After the null check, 'this' is autocast to a non-nullable type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

二、扩展属性

像扩展函数一样,Kotlin同样支持扩展属性,扩展函数没有backing field 不允许使用设定初始值,只能通过显式的getter/setter来实现。

1
2
val <T> List<T>.lastIndex: Int
    get() = size - 1

下面这样是错误的,不能知道设定初始值

1
val House.number = 1 // 错误:扩展属性不能有初始化器

三、伴生对象的扩展

伴生对象也可以直接定义扩展函数或属性,调用是与伴生对象的常规成员一样。

1
2
3
4
5
6
7
8
9
class MyClass {
    companion object { }  // 将被称为 "Companion"
}

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

fun main() {
    MyClass.printCompanion()
}

四、声明为类成员

可以在另一个类中声明当前类的扩展,仅在声明的当前作用域内有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()   // calls Host.printHostname()
        print(":")
        printPort()   // calls Connection.printPort()
    }

    fun connect() {
        /*...*/
        host.printConnectionString()   // calls the extension function
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
    //Host("kotl.in").printConnectionString()  // error, the extension function is unavailable outside Connection
}

如果扩展内部存在调用冲突则使用this关键字进行限定

1
2
3
4
5
6
class Connection {
    fun Host.getConnectionString() {
        toString()         // calls Host.toString()
        this@Connection.toString()  // calls Connection.toString()
    }
}

声明为成员扩展的可用open关键字进行修饰,子类中可以进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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()   // call the extension function
    }
}

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"
    DerivedCaller().call(Base())  // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
    DerivedCaller().call(Derived())  // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}

五、总结

  1. 扩展函数最好定义为可空接收者类型,避免空对象调用时发生NullPointerException

  2. 同时引入多个库时可能存在命名冲突,可以在导入时进行重命名

1
2
import com.library1.someFunction as lib1Function
import com.library2.someFunction as lib2Function
  1. 不要滥用扩展函数,滥用它们可能导致代码可读性下降。只有在逻辑上与类相关的操作才应该使用扩展函数。
  2. Kotlin 支持扩展属性,但它们不能有初始化器。这意味着你不能为扩展属性提供初始值,而只能在扩展的类中实现 getter
  3. 扩展函数和扩展属性不会真正地修改类,它们只是在调用时看起来像类的一部分。因此,扩展函数和属性不能访问类的私有成员
  4. 如果你的扩展函数与类自带的函数名相同,调用时会优先使用类自带的函数。因此,确保扩展函数的名字足够清晰,以避免与现有函数发生冲突。
  5. 扩展函数并不会真正地在被扩展的类中插入新的方法,而是通过静态方法调用实现。这意味着在性能要求高的情况下,可能需要谨慎使用扩展函数。
本文由作者按照 CC BY 4.0 进行授权

Kotlin学习笔记(7) - 可见修饰符

Kotlin学习笔记(9) - 数据类