Kotlin扩展

Kotlin与C#和Go类似,提供了扩展一个新功能的类,而不必继承类或使用任何类型的设计模式,如Decorator。 这是通过称为扩展名的特殊声明完成的。 Kotlin支持扩展功能和扩展属性。

扩展函数

要声明一个扩展函数,需要用一个接收器类型,即被扩展的类型来加上它的名字。 以下为MutableList <Int>添加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
}
扩展函数中的this关键字对应于接收器对象(在点之前传递的对象)。 现在,可以在任何MutableList <Int>上调用这样一个函数:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
当然,这个函数对于任何MutableList <T>是有意义的,可以将它通用化:
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 C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

此示例将打印“c”,因为被调用的扩展函数仅取决于参数c(C类)的声明类型。

该示例将打印“c”,因为类的calleIf的扩展函数具有成员函数,并且定义了扩展函数,其具有相同的接收器类型,相同的名称并且适用于给定的参数(该成员始终优先)。 例如:

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

如果调用c类型的c.foo(),它将打印“member”而不是“extension”。

但是,扩展函数可以重载具有相同名称但不同签名的成员函数,这是完全可行的:

class C {
    fun foo() { println("member") }
}

fun C.foo(i: Int) { println("extension") }

C().foo(1)的调用将打印“extension”。

可接受Null的接收器

请注意,可以使用可空(null)接收器类型定义扩展。 这样的扩展可以在一个对象变量上调用,即使它的值为null,并且可以在主体内检查this == null。 这样就可以在Kotlin中调用toString(),而无需检查null:检查发生在扩展函数内。
fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

扩展属性

与函数类似,Kotlin支持扩展属性:
val <T> List<T>.lastIndex: Int
    get() = size - 1
请注意,由于扩展名实际上并不将成员插入到类中,因此扩展属性没有有效的方式来添加后备字段。 这就是为什么不允许扩展属性的初始化器。 它们的行为只能通过明确提供getter / setter来定义。
val Foo.bar = 1 // error: initializers are not allowed for extension properties

伴随对象扩展

如果一个类定义了一个伴随对象,那么还可以定义该对象的扩展函数和属性:
class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}
就像伴随对象的常规成员一样,只能使用类名作为限定词:
MyClass.foo()

扩展范围

大多数时候在顶层定义扩展,即直接在包下:
package foo.bar

fun Baz.goo() { ... }
要在其声明包之外使用这样的扩展,需要在调用时导入它:
package com.web3.usage

import foo.bar.goo // importing all extensions by name "goo"
                   // or
import foo.bar.*   // importing everything from "foo.bar"

fun usage(baz: Baz) {
    baz.goo()
)

有关详细信息,请参阅导入。

声明扩展作为成员

在类中,可以为另一个类声明扩展名。 在这样的扩展中,有多个隐式接收器 - 可以在没有限定符的情况下访问对象成员。 声明扩展名的类的实例称为调度接收方,扩展方法的接收方型称为扩展接收方。

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}
在发送接收器的成员与分发接收器之间发生名称冲突的情况下,分发接收器优先。 要引用发送接收器的成员,可以使用合格的this语法。
class C {
    fun D.foo() {
        toString()         // calls D.toString()
        this@C.toString()  // calls C.toString()
    }
声明为成员的扩展可以被声明为在子类中打开(open)和覆盖。 这意味着这种函数调度对于调度接收器类型是虚拟的,但是关于扩展接收器类型是静态的。
open class D {
}

class D1 : D() {
}

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}
C().caller(D())   // prints "D.foo in C"
C1().caller(D())  // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1())  // prints "D.foo in C" - extension receiver is resolved statically

动机

在Java中,我们习惯使用名为“*Utils”的类:FileUtilsStringUtils等。java.util.Collections也属于这一类用法。 关于这些Utils类的令人不快的部分,如下代码所示:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
这些类名总是要明确写出来,但是可以使用静态导入并得到:
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
上面代码是不是更好的一点,但一般我们没有或很少使用强大的IDE的代码完成功能来完成。
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3