抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

委托

委托模式已被证明是实现继承的一个很好的替代方案,并且 Kotlin 本身就支持它,需要零样板代码。

Derived 类可以通过将其所有公共成员委托给指定对象来实现接口 Base:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

Derived 的超类型列表中的 by 子句表示 b 将在内部存储在 Derived 的对象中,并且编译器将生成 Base 的所有方法转发给 b。

覆盖委托实现的接口成员

但是请注意,以这种方式覆盖的成员不会从委托对象的成员中调用,委托对象只能访问其自己的接口成员实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Base {
val message: String
fun print()
}

class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
// This property is not accessed from b's implementation of `print`
override val message = "Message of Derived"
}

fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}

输出:

1
2
BaseImpl: x = 10
Message of Derived

委托属性

对于一些常见的属性,即使可以在每次需要它们时手动实现它们,但最好实现一次,将它们添加到库中,然后再使用它们。 例如:

  • Lazy 属性:该值仅在首次访问时计算。
  • Observable 属性:通知侦听器有关此属性的更改。
  • 将属性存储在 Map 中,而不是每个属性的单独字段。

为了涵盖这些(和其他)情况,Kotlin 支持委托属性:

1
2
3
class Example {
var p: String by Delegate()
}

语法为:val/var <property name>: <Type> by <expression>by 后面的表达式是一个委托,因为属性对应的 get()(和 set())会被委托给它的 getValue()setValue() 方法。
属性委托不必实现接口,但它们必须提供 getValue() 函数(以及用于 vars 的 setValue())。

1
2
3
4
5
6
7
8
9
10
11
import kotlin.reflect.KProperty

class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}

当从 p 读取时,它委托给 Delegate 的一个实例,来自 Delegate 的 getValue() 函数被调用。 它的第一个参数是从中读取 p 的对象,第二个参数包含对 p 本身的描述(例如,可以取其名称)。

同样,当复赋值给 p 时,将调用 setValue() 函数。 前两个参数是相同的,第三个保存被赋值的值:

标准委托

Kotlin 标准库为几种有用的委托提供工厂方法。

Lazy properties

lazy() 是一个接受 lambda 并返回 Lazy<T> 实例的函数,该实例可以用作实现惰性属性的委托。
第一次调用 get() 执行传递给 lazy() 的 lambda 并记住结果。 对 get() 的后续调用仅返回记住的结果。

默认情况下,惰性属性的评估是同步的:该值仅在一个线程中计算,但所有线程将看到相同的值。
如果初始化委托的同步不需要允许多个线程同时执行,请将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给lazy()。

如果确定初始化将始终发生在与您使用该属性的线程相同的线程中,可以使用 LazyThreadSafetyMode.NONE。 它不会产生任何线程安全保证和相关开销。

Observable properties

Delegates.observable() 接受两个参数:初始值和修改 handler。

每次赋值给属性时都会调用 handler(在执行分配之后)。 它具有三个参数:分配给的属性、旧值和新值:

如果想拦截分配并否决它们,请使用 vetoable() 而不是 observable()。 传递给 vetoable 的 handler 将在分配新属性值之前被调用。

委托给其他属性

一个属性可以将它的 getter 和 setter 委托给另一个属性。 这种委派可用于顶层和类属性(成员和扩展)。 委托属性可以是:

要将一个属性委托给另一个属性,请在委托名称中使用 :: 限定符,例如 this::delegateMyClass::delegate

1
2
3
4
5
6
7
8
9
10
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt

val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt

这可能很有用,例如,当想以向后兼容的方式重命名属性时:引入新属性,使用 @Deprecated 注释对旧属性进行注释,然后委托其实现。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// Notification: 'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}

把属性存储在 map 中

一个常见的用例是将属性值存储在 map 中。 这经常出现在诸如解析 JSON 或执行其他动态任务之类的应用程序中。
在这种情况下,可以将 map 实例本身用作委托属性的委托。

1
2
3
4
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}

委托属性通过与属性名称相关联的字符串键从该映射中获取值。

局部委托属性

可以把局部属性声明为委托属性

属性委托要求

对于只读属性 (val),委托应提供具有以下参数的运算符函数 getValue()

  • thisRef 必须与属性所有者的类型相同或超类型(对于扩展属性,它应该是被扩展的类型)。
  • property 必须是 KProperty<*> 类型或其超类型。

getValue() 必须返回与属性(或其子类型)相同的类型。

对于可变属性 (var),委托必须另外提供一个操作函数 setValue(),并带有以下参数:

  • thisRef 必须与属性所有者的类型相同或超类型(对于扩展属性,它应该是被扩展的类型)。
  • property 必须是 KProperty<*> 类型或其超类型。
  • value 必须与属性(或其超类型)具有相同的类型。

getValue() 和/或 setValue() 函数可以作为委托类的成员函数或扩展函数提供。 当需要将属性委托给最初不提供这些功能的对象时,后者很方便。
这两个函数都需要用 operator 关键字标记。

可以使用 Kotlin 标准库中的接口 ReadOnlyPropertyReadWriteProperty 将委托创建为匿名对象,而无需创建新类。
它们提供了所需的方法: getValue() 在 ReadOnlyProperty 中声明; ReadWriteProperty 对其进行了扩展并添加了 setValue()。
这意味着可以在需要 ReadOnlyProperty 时传递 ReadWriteProperty。

委托属性的翻译规则

在底层,Kotlin 编译器为某些类型的委托属性生成辅助属性,然后委托给它们。

例如,对于属性 prop,它会生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

1
2
3
4
5
6
7
8
9
10
11
class C {
var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用外部类 C 的一个实例,而 this::prop 是描述 prop 本身的 KProperty 类型的反射对象。

评论