首页 > 开发 > 综合 > 正文

Kotlin代理属性--官方文档翻译

2024-07-21 23:03:36
字体:
来源:转载
供稿:网友

代理属性 Delegated Properties

一些特定的常见类型的属性, 尽管我们可以在每次需要的时候实现他们, 但是如果我们一次把他们全部实现并放在一个库中, 这会非常方便, 包括:

延迟属性: 只在第一次访问的时候计算值

广播属性: 当属性的值改变时通知观察者

将数据存储在键值对中, 而不是独立的域中.

Kotlin提供的代理属性, 包含了这些(以及其他)例子:

class Example{

var p: String by Delegate()

}

语法是:val/var : by . 在by关键字后面的语句是delegate, 因为属性的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.'")

}

}

当我们从被代理给Delegate实例的p时,Delegate的getValue()方法被调用,

第一个参数是读取p所在的对象, 第二个参数保存p自身的描述(例如: 你可以获取它的名字). 例如:

val e = Example()

println(e.p)

打印结果:

Example@33a17727, thank you for delegating ‘p’ to me!

类似的, 当我们给p赋值时,setValue()方法被调用. 前两个参数是相同的, 第三个参数保存被赋的新值:

e.p = "NEW"

打印结果:

NEW has been assigned to ‘p’ in Example@33a17727.

关于代理对象的需求的说明可以在[这里]找到(delegated-properties.html#property-delegate-requirements).

需要注意的是从Kotlin 1.1之前你可以在方法或代码块中声明代理属性了, 代理属性不必声明为类的成员,例子.

标准库中的代理 Standard Delegates

Kotlin标准库为一些常用的代理提供了工厂方法.

延迟属性 Lazy

lazy()方法接收一个lamda作为参数并返回一个Lazy实例, 可以实现延迟加载:

第一次调用get()时执行传入lazy()的lambda表达式并保存结果, 后续对get()的调用只返回保存的结果.

val lazyValue: String by lazy {

println("computed!")

"Hello"

}

fun main(args: Array) {

println(lazyValue)

println(lazyValue)

}

该例子输出:

computed!

Hello

Hello

同步的

默认情况下, 延迟属性的计算是(synchronized): 只有一个线程计算该值, 其他的线程都会看见相同的值. 如果此初始化的步骤不需要同步, 多个线程可以同事执行初始化, 在lazy()方法中传入LazyThreadSafetyMode.PUBLICATION作为参数.

如果可以确保初始化过程只会在单个线程中执行, 可以用LazyThreadSafetyMode.NONE模式, 该模式不保证线程安全, 避免相关的开销.

监控属性 Observable

Delegates.observable()有两个参数: 初始值和变化观察器.

每次代理属性被赋予值的时候都会调用观察器(在赋值操作之后).观察器有三个参数:属性类型, 旧值和新值.

import kotlin.properties.Delegates

class User {

var name: String by Delegates.observable("") {

prop, old, new ->

println("$old -> $new")

}

}

fun main(args: Array) {

val user = User()

user.name = "first"

user.name = "second"

}

该例子输出

-> first

first -> second

如果你想终端赋值的过程并拒绝赋值, 用vetoable()替代observable().

observable()的观察器参数是在赋值之前被调用的.

使用Mapc存储属性 Storing Properties in a Map

在map中存储属性是一种常见使用方式.

这种情形在解析JSON或者其他"动态的"事情时经常出现.

在这种情况下, 你可以使用map的实例来代理一个代理属性.

classUser(val map: Map) {

val name: String by map

val age: Int by map

}

在这个例子中, 构造器接收一个map:

val user = User(mapOf(

"name" to "John Doe",

"age" to 25

))

代理属性从这个map接收值(通过String类型的key --- 作为属性的名字)

println(user.name) // Prints "John Doe"

println(user.age) // Prints 25

当使用MutableMap而不是只读的Map时, 对var也可以使用.

classMutableUser(val map: MutableMap) {

var name: String by map

var age: Int by map

}

本地代理属性 Local Delegated Properties (since 1.1)

你可以声明局部变量作为代理属性.

例如, 可以让局部变量成为lazy属性.

fun example(computeFoo: () -> Foo) {

val memoizedFoo by lazy(computeFoo)

if (someCondition && memoizedFoo.isValid()) {

memoizedFoo.doSomething()

}

}

memoizedFoo变量只会在第一次访问时计算.

如果someCondition失败了, 变量的值则完全不会进行计算.

属性代理的需求 Property Delegate Requirements

在此我们整理一下代理对象的需求.

只读

对于一个属性(例如:val), 代理必须提供一个接收下列参数的getValue函数:

thisRef--- 必须是_属性拥有者_相同类型或者是其超类(对于扩展属性 --- 则是其所扩展的属性)

property--- 必须是KProperty<*>类型或其超类,

这个函数必须返回和属性相同的类型, 或者其子类.

可变

对于的属性(比如var), 代理必须额外提供具备下列参数的setValue函数:

thisRef--- 与getValue()相同,

property--- 与getValue()相同,

new value --- 必须是与属性或其超类相同的类型

getValue()和/或setValue()函数可以用两种方式提供: 代理类的成员函数或者扩展函数.

后者在原有对象没有提供这些函数时非常方便.两种方式的函数都要使用operator关键字修饰.

代理类可以实现下面的接口之一, 包含operator方法的ReadOnlyProperty和ReadWriteProperty接口. 这些接口在Kotlin标准库中声明.

interface ReadOnlyProperty {

operator fun getValue(thisRef: R, property: KProperty<*>): T

}

interface ReadWriteProperty {

operator fun getValue(thisRef: R, property: KProperty<*>): T

operator fun setValue(thisRef: R, property: KProperty<*>, value: T)

}

翻译规则 Translation Rules

在代理属性的背后, Kotlin编译器生成一个辅助属性并代理给它. 比如, 对于属性prop, 会生成一个prop$delegate辅助属性, 访问器的代码就是简单的代理给这个附加的属性:

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类型.

提供代理 Providing a delegate (since 1.1)

通过定义provideDelegate操作符可以扩展创建属性实现所代理对象的逻辑.如果by右侧使用的对象定义了provideDelegate作为成员函数或者扩展函数, 这个函数会在创建属性代理时被调用.

One of the possible use cases ofprovideDelegateis to check property consistency when the property is created, not only in its getter or setter.

provideDelegate一个可能的用法是用来在创建属性期间检查属性的一致性, 而不是在getter或setter中.

比如你想在绑定前检查属性的名字, 可以这样写:

class ResourceLoader(id: ResourceID) {

operator fun provideDelegate(

thisRef: MyUI,

prop: KProperty<*>

): ReadOnlyProperty {

checkProperty(thisRef, prop.name)

// 创建代理

}

private fun checkProperty(thisRef: MyUI, name: String) { ... }

}

fun bindResource(id: ResourceID): ResourceLoader { ... }

class MyUI {

val image bybindResource(ResourceID.image_id)

val text bybindResource(ResourceID.text_id)

}

provideDelegate和getValue的参数相同:

thisRef--- 必须与属性的拥有者或其超类类型相同(对于扩展属性 -- 指被扩展的类)

property--- 必须是KProperty<*>类型或其超类.

TheprovideDelegatemethod is called for each property during the creation of theMyUIinstance, and it performs the necessary validation right away.

provideDelegate方法在每个MyUI实例创建期间都被调用, 并立即进行必要的检验.

如果没有这种在属性和其代理之间拦截的手段, 要明确的传入属性命, 这很不方便.

// 没有"provideDelegate"的情况下检查属性命

class MyUI {

val image by bindResource(ResourceID.image_id, "image")

val text by bindResource(ResourceID.text_id, "text")

}

fun MyUI.bindResource(

id: ResourceID,

propertyName: String

): ReadOnlyProperty {

checkProperty(this, propertyName)

// 创建委托

}

在生成的代码中,provideDelegate被调用, 以此来初始化辅助prop$delegate属性. 比较声明是val prop: Type by MyDelegate()的属性生成的代码和上面不提供provide Delegate函数的代码.

class C {

var prop: Type by MyDelegate()

}

// 在提供`provideDelegate`函数时, 这些代码由编译器生成

class C {

// 调用"provideDelegate"函数创建额外的"delegate"属性

private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)

val prop: Type

get() = prop$delegate.getValue(this, this::prop)

}

注意provideDelegate函数只影响辅助属性的创建, 并不影响getter和setter的生成.

本文为个人翻译的Kotlin官方文档, 原文连接:Delegated Properties


注:相关教程知识阅读请移步到kotlin教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表