Kotlin扩展

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

扩展函数

要声明一个扩展函数,需要用一个接收器类型,即被扩展的类型来加上它的名字。 以下为MutableList <Int>添加swap函数:

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

在函数名称之前声明通用类型参数,使其在接收器类型表达式中可用。 请参阅通用功能。

扩展程序被静态解析

扩展不会实际修改它们扩展的类。 通过定义扩展名,不将新成员插入到类中,而只能使用这种类型的变量上的点符号来调用新的函数。

扩展功能是静态调度的,即它们不是接收器类型的虚拟机。 这意味着被调用的扩展函数由调用该函数的表达式的类型决定,而不是在运行时评估该表达式的结果的类型。 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
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的扩展函数具有成员函数,并且定义了扩展函数,其具有相同的接收器类型,相同的名称并且适用于给定的参数(该成员始终优先)。 例如:

1
2
3
4
5
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }

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

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

1
2
3
4
5
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:检查发生在扩展函数内。
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-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}

扩展属性

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

伴随对象扩展

如果一个类定义了一个伴随对象,那么还可以定义该对象的扩展函数和属性:
1
2
3
4
5
6
7
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
就像伴随对象的常规成员一样,只能使用类名作为限定词:
1
MyClass.foo()

扩展范围

大多数时候在顶层定义扩展,即直接在包下:
1
2
3
package foo.bar
fun Baz.goo() { ... }
要在其声明包之外使用这样的扩展,需要在调用时导入它:
1
2
3
4
5
6
7
8
9
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()
)

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

声明扩展作为成员

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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语法。
1
2
3
4
5
class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // calls 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() // 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类的令人不快的部分,如下代码所示:
1
2
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
这些类名总是要明确写出来,但是可以使用静态导入并得到:
1
2
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
上面代码是不是更好的一点,但一般我们没有或很少使用强大的IDE的代码完成功能来完成。
联系我们

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

Copyright © 2015-2024

备案号:京ICP备15003423号-3