Kotlin与C#和Go类似,提供了扩展一个新功能的类,而不必继承类或使用任何类型的设计模式,如Decorator
。 这是通过称为扩展名的特殊声明完成的。 Kotlin支持扩展功能和扩展属性。
扩展函数
要声明一个扩展函数,需要用一个接收器类型,即被扩展的类型来加上它的名字。 以下为MutableList <Int>
添加swap
函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
|
val tmp = this[index1]
|
this[index1] = this[index2]
|
this[index2] = tmp
|
}
|
扩展函数中的
this
关键字对应于接收器对象(在点之前传递的对象)。 现在,可以在任何
MutableList <Int>
上调用这样一个函数:
val l = mutableListOf(1, 2, 3)
|
l.swap(0, 2)
|
当然,这个函数对于任何
MutableList <T>
是有意义的,可以将它通用化:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
|
val tmp = this[index1]
|
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"
|
|
|
return toString()
|
}
|
扩展属性
与函数类似,Kotlin支持扩展属性:
val <T> List<T>.lastIndex: Int
|
get() = size - 1
|
请注意,由于扩展名实际上并不将成员插入到类中,因此扩展属性没有有效的方式来添加后备字段。 这就是为什么不允许扩展属性的初始化器。 它们的行为只能通过明确提供
getter
/
setter
来定义。
伴随对象扩展
如果一个类定义了一个伴随对象,那么还可以定义该对象的扩展函数和属性:
class MyClass {
|
companion object { }
|
}
|
|
fun MyClass.Companion.foo() {
|
|
}
|
就像伴随对象的常规成员一样,只能使用类名作为限定词:
扩展范围
大多数时候在顶层定义扩展,即直接在包下:
package foo.bar
|
|
fun Baz.goo() { ... }
|
要在其声明包之外使用这样的扩展,需要在调用时导入它:
package com.web3.usage
|
|
import foo.bar.goo
|
|
import foo.bar.*
|
|
fun usage(baz: Baz) {
|
baz.goo()
|
)
|
有关详细信息,请参阅导入。
声明扩展作为成员
在类中,可以为另一个类声明扩展名。 在这样的扩展中,有多个隐式接收器 - 可以在没有限定符的情况下访问对象成员。 声明扩展名的类的实例称为调度接收方,扩展方法的接收方型称为扩展接收方。
class D {
|
fun bar() { ... }
|
}
|
|
class C {
|
fun baz() { ... }
|
|
fun D.foo() {
|
bar()
|
baz()
|
}
|
|
fun caller(d: D) {
|
d.foo()
|
}
|
}
|
在发送接收器的成员与分发接收器之间发生名称冲突的情况下,分发接收器优先。 要引用发送接收器的成员,可以使用合格的this语法。
class C {
|
fun D.foo() {
|
toString()
|
this@C.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
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()
|
}
|
}
|
|
class C1 : C() {
|
override fun D.foo() {
|
println("D.foo in C1")
|
}
|
|
override fun D1.foo() {
|
println("D1.foo in C1")
|
}
|
}
|
C().caller(D())
|
C1().caller(D())
|
C().caller(D1()) |
动机
在Java中,我们习惯使用名为“
*Utils
”的类:
FileUtils
,
StringUtils
等。
java.util.Collections
也属于这一类用法。 关于这些
Utils
类的令人不快的部分,如下代码所示:
|
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
|
这些类名总是要明确写出来,但是可以使用静态导入并得到:
|
swap(list, binarySearch(list, max(otherList)), max(list))
|
上面代码是不是更好的一点,但一般我们没有或很少使用强大的IDE的代码完成功能来完成。