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
,并且可以在主体内检查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() }
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
*Utils
”的类:FileUtils
,StringUtils
等。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的代码完成功能来完成。