在 Kotlin 中,委托不仅适用于类,也适用于属性。与传统属性直接由字段支持不同,委托属性将 get 和 set 的职责交给了一段单独的代码块。这样做的好处是可以将某些通用功能抽象出来,供多个相似的属性共享,从而实现属性逻辑的复用,提升代码的效率和可维护性。
属性委托示例
我们来看一个名为 Example
的类,其中包含两个 String
类型的属性:firstProp
和 secondProp
。如果我们希望这两个属性具有相同的格式化规则,可以为它们各自实现一个 setter 方法:
class Example {
var firstProp: String = ""
set(value) {
// 移除所有元音,并将字符串转为大写
field = value.replace(Regex("[aeiouAEIOU]"), "").uppercase()
}
var secondProp: String = ""
set(value) {
// 移除所有元音,并将字符串转为大写
field = value.replace(Regex("[aeiouAEIOU]"), "").uppercase()
}
}
代码说明
在这个例子中,我们给两个属性都定义了 set 函数,用来移除输入值中的所有元音字母并将其转换为大写。然而,这种做法会导致代码重复,不利于测试和维护。
一种更高效的写法是将 setter 委托出去,代码如下:
class Example {
var firstProp: String by Formatter()
var secondProp: String by Formatter()
}
代码说明
委托属性的声明方式是通过 by
关键字指定使用的委托对象。语法为:
val/var <属性名>: <类型> by <委托实例>
接下来我们看看如何实现这个 Formatter
委托类。
实现属性委托
属性委托类必须实现 getValue()
方法,若属性为 var
类型,还需实现 setValue()
方法。
在上面的例子中,Formatter
是负责控制 firstProp
和 secondProp
的 getter 和 setter 的委托类,实现如下:
import kotlin.reflect.KProperty
class Formatter {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value.replace(Regex("[aeiouAEIOU]"), "").uppercase()
}
}
代码说明
自定义属性委托类时,需要定义两个操作符函数:
-
getValue()
:负责返回属性的当前值; -
setValue()
:负责设置属性的新值。
这两个函数可以访问包含该属性的类(thisRef
)以及属性本身的元数据(KProperty
)。
参数详解示例
import kotlin.reflect.KProperty
class AnotherExample {
val stringProp: String by Delegate()
fun foo(): String = ""
}
class Delegate {
private var curValue = ""
operator fun getValue(thisRef: AnotherExample, property: KProperty<*>): String {
println(thisRef.stringProp + thisRef.foo()) // 可通过 thisRef 访问其他成员
return curValue
}
}
代码说明
在这个例子中,我们将一个只读属性 val
委托给 Delegate
类,因此只需要提供 getValue()
方法。
-
thisRef
参数表示拥有该属性的对象(这里是AnotherExample
实例)。 -
property
参数是一个KProperty
实例,用于访问属性的元信息,例如:
println(property.name) // 输出属性名 stringProp
如果将属性改为 var
,则还需要提供 setValue()
方法:
class AnotherExample {
var stringProp: String by Delegate()
}
class Delegate {
private var curValue = ""
operator fun getValue(thisRef: AnotherExample, property: KProperty<*>): String {
return curValue
}
operator fun setValue(thisRef: AnotherExample, property: KProperty<*>, value: String) {
println("属性 ${property.name} 的新值为: $value")
curValue = value
}
}
匿名委托对象(Anonymous Delegates)
Kotlin 允许你使用匿名对象来创建委托,而不需要额外定义一个类。只需要使用标准库提供的接口即可:
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
-
ReadOnlyProperty
接口只需实现getValue()
; -
ReadWriteProperty
继承自ReadOnlyProperty
并添加了setValue()
方法。
示例:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun anonymousDelegate() = object : ReadWriteProperty<Any?, String> {
var curValue = ""
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return curValue
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
curValue = value
println("属性 ${property.name} 的新值是: $value")
}
}
fun main() {
val readOnlyString: String by anonymousDelegate()
var readWriteString: String by anonymousDelegate()
readWriteString = "Hello!" // 输出:属性 readWriteString 的新值是: Hello!
}
代码说明
这个例子中我们定义了一个 anonymousDelegate()
函数,返回一个匿名对象,它实现了 ReadWriteProperty
接口。你可以在函数中局部使用这样的委托对象,这就是 Kotlin 所支持的 局部委托属性(local delegated properties)。
委托给另一个属性
Kotlin 还允许你将一个属性委托给另一个属性。示例:
class Example {
private var _counter = 0
var counter: Int
get() = _counter
set(value) {
_counter = value
println("Counter 设置为 $value")
}
var anotherCounter: Int by this::counter
}
fun main() {
val example = Example()
example.anotherCounter = 5 // 输出:Counter 设置为 5
}
代码说明
anotherCounter
使用 by this::counter
将委托权交给了 counter
属性。因此访问 anotherCounter
实际上等价于访问 counter
。
如果要委托给另一个类的属性,只需将 this
替换为那个类的实例名即可。
最佳实践
-
尽量使用标准库中的委托类
Kotlin 标准库提供了lazy
、observable
、map-based
等常用委托,能减少样板代码,建议优先使用。 -
保持委托单一职责
委托类应只处理一种逻辑,避免变得过于复杂,难以测试。 -
不要滥用委托机制
并不是所有属性都需要委托。只有在确实能提高复用、减少重复代码时才建议使用。
总结
本文介绍了 Kotlin 中强大的属性委托机制。我们学习了:
-
如何使用
getValue()
和setValue()
实现自定义委托; -
如何使用匿名对象来创建委托;
-
如何将属性委托给另一个属性;
-
以及推荐的使用最佳实践。
属性委托不仅使代码更加清晰和复用性更强,还可以增强系统的扩展能力,是 Kotlin 提供的一项非常实用的语言特性。