26:kotlin 类和对象 -- 委托属性(Delegation properties )
尽管每次需要时都可以手动实现一些常见类型的属性,但将它们实现一次、添加到库中以便以后重用会更为方便
- 懒加载属性:仅在首次访问时计算值。
- 可观察属性:监听器会收到有关此属性更改的通知。
- 将属性存储在映射中而不是为每个属性单独创建字段。
为了涵盖这些(以及其他)情况,kotlin
支持委托属性(delegated properties)
语法如下:val/var <property name>: <Type> by <expression>
。在 by
之后的表达式是一个委托,因为与属性对应的 get()
(和 set()
)将被委托给其 getValue()
和 setValue()
方法。属性委托不必实现一个接口,但它们必须提供一个 getValue()
函数(对于 var
还需要提供 setValue()
)
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.")
}
}
class Example {
var p: String by Delegate() // 委托
}
fun main() {
Example().p = "王老吉" // 王老吉 has been assigned to 'p' in com.example.Example@6acdbdf5.
println(Example().p) // com.example.Example@7a1ebcd8, thank you for delegating 'p' to me!
}
当你从委托给Delegate
的实例中p
中读取属性时,将调用Delegate
的getValue()
函数。第一个参数是你从中读取p
的对象,而第二个参数保存了p
属性本身的描述
你可以在函数或代码块内声明一个委托属性;它不必是类的成员
标准委托
kotlin
为重用代理定义了模板方法
懒加载属性
lazy()
是一个接受 lambda
并返回 Lazy<T>
实例的函数,可用作实现懒加载属性的委托。第一调用 get()
,会执行lambda
表达式并记住结果,之后的调用直接返回结果
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
// computed!
// Hello
// Hello
}
默认情况下,属性懒懒加载是同步的,在一个线程中进行,其他线程看到相同的结果。如果允许多个线程同时初始化,使用LazyThreadSafetyMode.PUBLICATION
参数。
val lazyValue: String by lazy (LazyThreadSafetyMode.PUBLICATION){
println("computed!")
"Hello"
}
如果确信属性初始化和使用是在同一个线程中进行的,可以使用LazyThreadSafetyMode.NONE
参数,但是不保证线程安全
可观察属性
Delegates.observable()
接受两个参数:初始值和用于处理修改的处理程序。
每次你对属性进行赋值时(在赋值完成后),处理程序都会被调用。它有三个参数:被赋值的属性、旧值和新值
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new -> println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first" // <no name> -> first
user.name = "second" // first -> second
}
如果你想拦截赋值并否决它们,使用 vetoable()
而不是 observable()
。传递给 vetoable()
的处理程序将在分配新属性值之前被调用
class User {
var name: String by Delegates.vetoable("<no name>") {
prop, old, new ->
println("$old -> $new")
true
}
}
fun main() {
val user = User()
user.name = "first" // <no name> -> first
user.name = "second" // first -> second
}
委托给另一个属性
一个属性可以将其 getter
和 setter
委托给另一个属性。这样的委托对于顶层和类属性(成员和扩展)都是可用的
委托属性可:
- 顶层属性
- 同一类的成员或扩展属性
- 另一个类的成员或扩展属性
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
注解标记旧属性,并委托其实现
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// 提示: oldName: Int' is deprecated. Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}
在Map中存储属性
一个常见的用例是将属性的值存储在Map
中。这在解析JSON
或执行其他动态任务的应用程序中经常发生。在这种情况下,您可以使用Map
实例本身作为委托属性的委托
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
// 构造方法接收一个Map
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
}
可变的Map
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
局部委托变量
可以将局部变量声明为委托属性。例如,可以使局部变量成为延迟加载的
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
The memoizedFoo
variable will be computed on first access only. If someCondition
fails, the variable won’t be computed at all.
属性委托的要求
对于只读属性val
,委托应提供一个带有以下参数的getValue()
thisRef
必须与属性所有者的类型相同,或者是其超类型(对于扩展属性,它应该是被扩展的类型)。property
必须是KProperty<*>
类型或其超类型
getValue()
必须返回与属性相同的类型(或其子类型)
import kotlin.reflect.KProperty
class Resource
class Owner {
val valResource: Resource by ResourceDelegate()
}
class ResourceDelegate {
operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
return Resource()
}
}
对于可变属性var
,委托还必须提供一个带有以下参数的setValue()
thisRef
必须与属性所有者的类型相同,或者是其超类型(对于扩展属性,它应该是被扩展的类型)property
必须是KProperty<*>
类型或其超类型value
必须是与属性相同的类型(或其超类型)
import kotlin.reflect.KProperty
class Resource
class Owner {
var varResource: Resource by ResourceDelegate()
}
class ResourceDelegate(private var resource: Resource = Resource()) {
operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
return resource
}
operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
if (value is Resource) {
resource = value
}
}
}
getValue()
/setValue()
函数可以作为委托类的成员函数或扩展函数提供(当委托类没有这两个函数时可以定义扩展函数)。这两个函数都需要用operator
关键字标记。
可以通过使用kotlin
标准库中的ReadOnlyProperty
和ReadWriteProperty
接口,以匿名对象的方式创建委托,而无需创建新的类。它们提供了所需的方法:getValue()
在ReadOnlyProperty
中声明;ReadWriteProperty
继承了它并添加了setValue()
。这意味着您可以在期望ReadOnlyProperty
的任何地方传递一个ReadWriteProperty
。
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class Resource
fun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty<Any?, Resource> =
object : ReadWriteProperty<Any?, Resource> {
var curValue = resource
override fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValue
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
curValue = value
}
}
val readOnlyResource: Resource by resourceDelegate()
var readWriteResource: Resource by resourceDelegate()
fun main() {
readWriteResource = Resource()
println(readOnlyResource)
}
委托属性转换规则
在底层,kotlin
编译器为某些类型的委托属性生成辅助属性,然后将委托交给这些辅助属性
出于优化目的,编译器在一些情况下不会生成辅助属性。通过委托到另一个属性的示例学习有关优化的信息。
例如,对于属性prop
,它生成隐藏的属性prop$delegate
,而访问器的代码简单地委托给这个附加属性
class C {
var prop: Type by MyDelegate()
}
// 上边的编码被编译为一下内容
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)
}
编译器在参数中提供了关于prop
的所有必要信息:第一个参数 this
指的是外部类 C
的一个实例,而 this::prop
是一个 KProperty
类型的反射对象,描述了 prop
本身
优化委托属性的情况
如果委托是以下情况之一,将省略 $delegate
字段
- 引用属性
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
- 命名对象
object NamedObject {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}
val s: String by NamedObject
- 同一模块中,使用
backing field
和默认getter()
的final val
属性
val impl: ReadOnlyProperty<Any?, String> = ...
class A {
val s: String by impl
}
- 常量表达式、枚举项、
this
、null
。this
示例
class A {
operator fun getValue(thisRef: Any?, property: KProperty<*>) ...
val s by this
}
委托给另一个属性时的转换规则
当委托给另一个属性时,编译器生成对所引用属性的直接访问。这意味着编译器不会生成字段 prop$delegate
。这种优化有助于节省内存
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
prop
变量的属性访问器直接调用 impl
变量,跳过了委托属性的 getValue
和 setValue
运算符,因此不需要 KProperty
引用对象。
对于上述代码,编译器生成了以下代码
class C<Type> {
private var impl: Type = ...
var prop: Type
get() = impl
set(value) {
impl = value
}
fun getProp$delegate(): Type = impl // This method is needed only for reflection
}
提供委托
通过定义 provideDelegate
运算符,您可以扩展创建属性实现委托的对象的逻辑。如果在 by
右侧使用的对象定义了 provideDelegate
作为成员或扩展函数,那么将调用该函数来创建属性委托实例
provideDelegate
的一种可能用例是在属性初始化时检查其一致性
例如,要在绑定之前检查属性名称,您可以编写如下代码
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>
): ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
// create delegate
return ResourceDelegate()
}
private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
class MyUI {
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
val image by bindResource(ResourceID.image_id)
val text by bindResource(ResourceID.text_id)
}
provideDelegate
的参数与 getValue
相同:
thisRef
必须与属性所有者的类型相同,或者是其超类型(对于扩展属性,它应该是被扩展的类型);property
必须是KProperty<*>
类型或其超类型。
provideDelegate
方法在创建 MyUI
实例期间为每个属性调用,并立即执行必要的验证。
如果没有拦截属性与其委托之间的绑定的能力,要实现相同的功能,您将不得不显式传递属性名称,这不是很方便
// Checking the property name without "provideDelegate" functionality
class MyUI {
val image by bindResource(ResourceID.image_id, "image")
val text by bindResource(ResourceID.text_id, "text")
}
fun <T> MyUI.bindResource(
id: ResourceID<T>,
propertyName: String
): ReadOnlyProperty<MyUI, T> {
checkProperty(this, propertyName)
// create delegate
}
在生成的代码中,provideDelegate
方法被调用来初始化辅助的 prop$delegate
属性。比较具有属性声明 val prop: Type by MyDelegate()
的生成代码与上面生成的代码(当不存在 provideDelegate
方法时)
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler
// when the 'provideDelegate' function is available:
class C {
// calling "provideDelegate" to create the additional "delegate" property
private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
请注意,provideDelegate
方法仅影响辅助属性的创建,不影响为 getter
或 setter
生成的代码
使用标准库中的 PropertyDelegateProvider
接口,可以创建委托提供程序而无需创建新的类
val provider = PropertyDelegateProvider { thisRef: Any?, property ->
ReadOnlyProperty<Any?, Int> {_, property -> 42 }
}
val delegate: Int by provider
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!