转载: Kotlin 代理 | Sunmoon 的博客
# Kotlin 代理
Java 中的代理很烦很繁,而 Kotlin 中的代理却看起来很简单。我们来学学 Kotlin 中代理的用法吧。
本文整理自 Delegated Properties。
有些通用类型的属性,尽管我们可以在需要每次都自己实现对于某些常用类型的属性,尽管我们可以在需要用于这些属性时每次都自行实现,但如果能一次性实现所有这些属性,并将其封装到库中可能是更好的方式。比如:
- 懒加载属性: 这些属性的值在首次使用时才生成
- observable 属性:每当属性值发生变化时监听器会收到通知
- 将属性值保存到 map,而不是为每个属性定义一个单独的字段
# 代理
针对这类情形 Kotlin 提供了代理属性 ,语法是 val/var <property name>: <Type> by <expression>
。 by
关键字后面的表达式即代理 。 属性对应的 get()
和 set()
方法会被代理到 <expression>
对应对象的 getValue()
和 setValue()
方法。属性代理不必实现任何接口,但必须满足以下条件:
- 为
val
属性提供getValue()
方法 - 为
var
性提供getValue()
和setValue()
方法
当然,Kotlin 标准库中提供了 ReadOnlyProperty
和 ReadOnlyProperty
接口包含这里提到的方法。 Delegate
可以实现这两个接口。
比如这里的 Delegate
就是一个代理:
1 | class Delegate { |
假如有以下 Example
:
1 | class Example { |
当你读取 Example.p
字段的值时,由于它被代理到一个 Deleage
实例,这个 Delegate
实例的 getValue()
方法会被调用。 getValue()
的参数如下:
- 第一个参数为
Example
实例,即你读取 p 字段时的那个实例 - 第二个参数是关于 p 的描述,它在 Kotlin 中被封装与
KProperty
对象
所以最终 getValue()
输出 Example@33a17727, thank you for delegating ‘p’ to me!
注意:
getValue()
方法参数的必须是thisRef: Example?, property: KProperty<*>
。thisRef
的类型必须跟属性的类型相同,或者是属性类型的父类。也可以放宽为Any?
,这样能让Delegate
更加通用。property
必须为KProperty<*>
或其父类getValue()
方法的返回值必须跟属性类型相同,或者是属性类型的父类setValue()
方法的第三个参数 new value,必须跟属性类型相同或是其父类getValue()
和setValue()
可以是 Delegate 的成员方法,也可是其扩展方法。对于原先并没有定义这些方法的对象而言,扩展方法更为方便。但无论是作为成员方法还是扩展方法,都需要添加operator
修饰符
为什么对 getValue()
和 setValue()
方法有上述规定呢,我们看看 Kotlin 编译器是如何处理 Delegate 的。实际上,对于每个代理属性,Kotlin 编译器都会生成一个辅助的属性。比如,对于 prop
属性,会生成一个隐藏的 prop$delegate
属性,而 get()
和 set()
方法最后只是简单地代理到这个辅助属性。
1 | class C { |
我们可以在 Idea 中通过 Tools > Kotlin > Show Kotlin Bytecode > Decompile 看到对应的 Java 代码,确实如这里所述。
# 标准代理
# Lazy 代理
lazy () 是一个可接收 lambda 表达式作为参数的方法,它返回一个 Lazy<T>
实例,该实例作为代理来实现懒加载功能。第一次调用 get()
时会执行传给 lazy()
方法的 lambda 并且保留其结果,之后再调用 get()
时会直接返回该结果。缺省情况下对懒加载属性的计算是同步的
- LazyThreadSafetyMode.SYNCHRONIZED - 默认的懒加载方式,会锁定当前属性以保证仅在一个线程中对其初始化
- LazyThreadSafetyMode.PUBLICATION - 初始化方法在并发访问未被初始化的属性时可多次被调用,但仅最先被返回的值作为属性的值
- LazyThreadSafetyMode.NONE - 对属性的访问不加任何锁。如果当前对象在多个线程中被访问,则属性的值不确定
# Observable 代理
Delegate.observable()
有两个参数:属性的初始值和监听器。当给属性赋值时 (即执行赋值操作后) 监听器会被调用。监听器被调用时会接收到三个参数:被赋值的属性,属性的旧值,属性的新值。
vetoable()
跟 observable()
类似,但它会在属性赋值前被调用。
# Map
一个常用的场景是在 map 中保存属性值。这种场景在解析 JSON 或类似的” 动态” 编程时很常见。这时可以使用 map 实例作为属性的代理。类似这样:
1 | class User(val map: Map<String, Any?>) { |
代理属性 (例如 name
从 map) 中获取值,取值时的 key 即为属性的名字。