Kotlin学习之-5.13 代理属性
2017-07-11 20:14
169 查看
Kotlin学习之-5.13 代理属性
有一些常见的属性,尽管我们可以每次需要他们的时候手动实现他们,但是最好还是可以实现一次然后随处可用,并把它放到库里。 这些例子包括:懒加载属性:属性值会在第一次访问的时候被计算
可观察的属性:监听器会得到属性变化的通知
存储在map中的属性,而不是单独给每个属性一个单独的变量
为了覆盖这些情况,Kotlin支持代理属性
class Example { var p: String by Delegate() }
语法是:
val/var <property name>: <Type> by <expression>。 在
by之后的表达式就是代理,因为属性对应的
get()和
set()方法会被代理成它的
getValue()和
setValue()方法。属性代理不是必须实现接口,但是他们必须提供
getValue()和
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.'") } }
荡我们读取
p的时候,他会代理到一个
Delegate对象,然后
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
代理对象的具体要求在下面会详细描述。
注意从Kotlin v1.1 开始,可以在一个函数或者代码块中声明一个代理属性,它不是必须是一个类的成员。
标准代理
Kotlin标准库提供一些有用的代理工厂方法。懒函数
lazy()函数接收一个lambda表达式并且返回一个
Lazy<T>的实例,这个实例可以当做一个实现了懒属性的代理:第一次调用
get()时执行传递给
lazy()函数的lambda表达式并记住它的结果,后续的调用
get()就直接返回记住的结果。
val lazyValue: String by lazy { println("computed!") "Hello" } fun main(args: Array<String>) { println(lazyValue) println(lazyValue) }
这个例子会输出
computed! Hello Hello
默认情况下,懒属性的评估是同步的(synchronized):只会在一个线程中计算它的值,并且所有线程看到的值是相同的。如果不要求代理初始化的同步,那么多个线程都可以同时执行它,传递
LazyThreadSafetyMode.PUBLICATION当做
lazy()函数的一个参数。并且,如果你确认它的初始化总是在一个线程上执行,你可以使用
LazyThreadSafeMode.NONE模式,这样就不会有任何线程安全的保障以及相应的开销。
可观察属性(Observable)
Delegates.observalbe()接收两个参数:初始值和一个对于修改的回调。每次当我们给属性赋值的时候,赋值完成后,回调都会被调用。 它有三个参数:被赋值的属性,属性的原值,属性的新值。
import kotlin.properties.Delegates class User { var name: String by Deleates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second" }
这个例子会输出
<no name> -> first first -> second
如果你想要能够拦截一个赋值并改写它,使用
vetoable来替代
observable()。这样传递给
vetoble的回调是在赋值之前调用。
在Map中存储属性
一种常见的情况是在Map中存储属性。当解析JSON或者处理其他动态的事情时经常需要这么做,你可以使用map对象本身作为一个代理属性的代理。class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map }
在这个例子中,构造器接收一个map:
val user = User(mapOf( "name" to "John Dow", "age" to 25 ))
代理属性从map中接收值,通过属性的键值对
println(user.name) // 输出"John Doe" println(user.age) // 输出25
对于
var属性也可以使用
MutableMap
class MutableUser(val map: MutableMap<String, Any?>) { var name: String by map var age: Int by map }
局部代理属性(从Kotlin v1.1)
你可以什么局部变量作为代理属性。例如,你可以让一个局部变量延迟加载:fun example(computeFoo: () -> Foo) { val memorizedFoo by lazy(computeFoo) if (someCondition && memorizedFoo.isValid()) { memorizedFoo.doSomething() } }
变量
memorizedFoo的值仅会在第一次被访问时计算。如果
someConditon的结果是‘否’,那么变量值永远都不会被计算。
属性代理的要求
这里我们总结一下代理对象的要求。对于一个只读属性(例如
val),一个代理必须提供一个名为
getValue的函数,这个函数接收下面两个参数:
thisRef, 必须和属性拥有者是一样的,或者是它的父类。
property,必须是
KProperty<*>类型或者它的父类。
这个函数返回值的类型必须和属性的类型是一样的,或者是它的子类。
对于可变属性(
var),一个代理必须附加提供一个名为
setValue的函数,这个函数接收下面三个参数:
thisRef和
getValue()中的一样
property和
getValue()中的一样
new value, 类型必须和属性的类型一样活着是它的父类。
getValue()和
setValue()函数可以被用作代理类的成员函数或者扩展函数。后者非常帮助,当你想要代理一个对象,但是它并没有提供这些方法。这两个函数都需要用关键字
operator来标记。
代理类可以实现两个接口中的任意一个,
ReadOnlyProperty和
ReadWriteProperty,他们都包含必须的
operator操作符函数。 这些接口在Kotlin标准库中已经定义。
interface ReadOnlyProperty<in R, out T> { operator fun getValue(thisRef: R, property: KProperty<*>): T } interface ReadWriteProperty<in R, out t> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
翻译规则
在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) }
Kotlin编译器给
prop属性在参数中提供所有可能需要的信息:第一个参数
this引用一个外部类
C的实例,
this::prop是一个
KProperty类型的反射对象,可以表示
prop自己。
注意
this::prop这样的语法参考bound callable reference仅在Kotlin v1.1之后可用.
提供一个代理(Kotlin v1.1之后支持)
通过定义provideDelegate操作符,你可以扩展创建对象的逻辑,这些对象属性的实现是代理的。如果对象用在
by的右手边,定义
provideDelegate当做一个成员或者扩展函数,这个函数可以被调用来创建属性代理对象。
一个可能的
provideDelegate用例是在创建属性的时候检查属性一致性,而不仅仅是在他的getter 和setter方法。
例如,如果你想要在绑定之前检查属性名,你可以这样写:
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) } private fun checkProperty(thisRef: MyUI, name: String) { } } fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { } class MyUI { val image by bindResource<ResourceID.image_id) val text by bindResource<ResourceID.text_id) }
provideDelegate的参数是和
getValue的参数是一样的:
thisRef, 类型必须和属性所有者的类型是一样的或者是他的父类
property,类型必须是
KProperty<*>或者是它的父类
provideDelegate方法在
MyUI实例创建的时候会为每个属性调用,并且他会立即执行必要的验证。
如果没有在属性和他的代理之间拦截绑定的能力,想要实现同样的功能你必须显式地传递属性名,这样不是很方便。
// 不使用'provideDelegate' 功能来检查属性名, 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) }
在生成的代码中,
provideDelegate方法会被调用来初始化附加的
prop$delegate属性。 比较属性声明的代码,
val prop: Type by MyDelegate()
class C { val 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
PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发
相关文章推荐
- Checklists学习日志之UITableView的全部属性、方法以及代理方法执行顺序
- swift基础学习传值[属性传值、代理、block代码块、等]
- Kotlin代理属性--官方文档翻译
- Kotlin学习(十九): 属性自动生成方法所引发问题
- Kotlin学习(十八): 委托模式(Delegate)和委托属性(Delegate Properties)
- iOS学习笔记---UIScrollView 常见属性 不能滚动 常见代理方法
- Kotlin学习(9):属性和字段
- Kotlin相关基础及与Java的不同 的 笔记(仨) --拓展方法和属性代理
- Kotlin学习之-5.12 代理模式
- Kotlin(2.13)代理属性
- IOS学习笔记(页面传值:属性传值,协议代理传值,闭包传值)
- 学习kotlin第十天_对象、委托、委托属性
- Kotlin 官方学习教程之属性和字段
- Kotlin代理属性--官方文档翻译
- Kotlin学习之-5.2 属性和成员
- IOS学习 UITextField 属性和代理
- 学习kotlin第七天_类与继承、属性与字段
- Kotlin 从学习到 Android 第七章 属性和字段
- Kotlin学习笔记--继承、接口、代理、委托、单例
- Kotlin学习(四)—— 类和对象,继承,覆盖,抽象类,属性和字段,接口,可见性修饰符,扩展