前言
熟悉 Kotlin 的人可能知道, 类代理是一种基于父类或者接口的实现, 而在代理属性这边没有这种限制, 而且这些代理对象的公共方法的参数中还包含了委托对象, 这意味着在代理对象中也可以调用委托对象的公共方法. Kotlin 的标准库中就包含了许多使用代理属性的实现, 比如 lazy.
免费获取更多安卓开发架构的资料 (包括 Fultter, 高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发(ReactNative+Weex) 和一线互联网公司关于 Android 面试的题目汇总可以加入[腾讯 @安卓中高级进阶]
正文
我们先来学习下写标准库的大佬怎么玩的, lazy 的用法很简单:
- val num by lazy {
- BigInteger.valueOf(120).modPow(BigInteger.valueOf(120))
- }
我们假设 num 的获取是耗时操作, 而且我们还不一定要用到它, 一个比较好的策略就是惰性求值, 用到时再去获取, 并把结果缓存起来避免重复的运算, 提高代码的性能, lazy 提供的就是这样一种机制.
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
这里是一个高阶函数, 接受一个 lambda 作为参数, 返回了一个 SynchronizedLazyImpl 的对象, 现在还看不出是什么东西, 我们再往里面看:
- private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
- private var initializer: (() -> T)? = initializer
- @Volatile private var _value: Any? = UNINITIALIZED_VALUE
- // final field is required to enable safe publication of constructed instance
- private val lock = lock ?: this
- override val value: T
- get() {
- val _v1 = _value
- if (_v1 !== UNINITIALIZED_VALUE) {
- @Suppress("UNCHECKED_CAST")
- return _v1 as T
- }
- return synchronized(lock) {
- val _v2 = _value
- if (_v2 !== UNINITIALIZED_VALUE) {
- @Suppress("UNCHECKED_CAST") (_v2 as T)
- } else {
- val typedValue = initializer!!()
- _value = typedValue
- initializer = null
- typedValue
- }
- }
- }
- override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
- override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
- private fun writeReplace(): Any = InitializedLazyImpl(value)
- }
哦, 这是一个实现了 Lazy 接口的类, 我们可以看到, value 的 get 方法使用了 synchronized 关键字来确保线程安全, 我们传入的 lambda 会在这里被调用计算出一个结果, 然后结果被缓存在_value 中, 下次再访问就不会重新计算结果了.
而 Lazy 的结构如下:
- public interface Lazy<out T> {
- public val value: T
- public fun isInitialized(): Boolean
- }
结构很简单, 没什么东西, 我们再回到 lazy 函数的重载方法:
- public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
- when (mode) {
- LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
- LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
- LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
- }
哦哟, 使用这个方法我们可以显式指定一个 LazyThreadSafetyMode, 从名字上看它跟线程安全有关系, 而且每个模式都使用了不同的 Lazy 实现, 除了我们刚刚讨论的 SynchronizedLazyImpl, 还有其它一些.
先来看 LazyThreadSafetyMode, 这是一个枚举类, 支持三种模式:
SYNCHRONIZED 使用锁来确保只有一个线程来求值.
PUBLICATION 允许多个线程来初始化值, 但是只有第一个返回的值有效.
NONE 允许多个线程来初始化值, 但是行为就不确定了.
意思就是我们的 App 运行在单线程里我们就可以直接把 mode 传为 NONE 啰, 避免加锁带来的开销呗, 那在 Android 开发过程中, 我们可以这么用:
- private val rv by lazy(LazyThreadSafetyMode.NONE) {
- findViewById<RecyclerView>(R.id.rv)
- }
因为系统只会在 UI 线程上操作 UI, 所以我们不需要担心有什么并发访问, 稍加包装我们甚至可以自己实现一个 KotterKnife.
再来看看 None 模式下使用的 UnsafeLazyImpl:
- internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
- private var initializer: (() -> T)? = initializer
- private var _value: Any? = UNINITIALIZED_VALUE
- override val value: T
- get() {
- if (_value === UNINITIALIZED_VALUE) {
- _value = initializer!!()
- initializer = null
- }
- @Suppress("UNCHECKED_CAST")
- return _value as T
- }
- override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
- override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value)
- }
还是主要看 value 的 get 方法, 我们可以看到, get 方法只会检查 value 有没有被赋值, 然后计算出一个结果或者返回缓存的值, 但是这里并没有加锁, 就不会保证线程安全, 常见的并发问题都有可能在这里发生.
最后到了 PUBLICATION, 它也允许多线程访问, 但是跟 NONE 有些微妙的差别, 来看一个小例子, 来帮我们理解 PUBLICATION 的行为:
- class CacheThread(val lazyValue: BigInteger) : Thread() {
- override fun run() {
- super.run()
- Thread.sleep(250)
- println("${this::class.java.simpleName} $lazyValue")
- }
- }
- class NetworkThread(val lazyValue: BigInteger) : Thread() {
- override fun run() {
- super.run()
- Thread.sleep(300)
- println("${this::class.java.simpleName} $lazyValue")
- }
- }
我们模拟了两个线程执行耗时操作, 一个取缓存, 一个取网络数据, 他们都需要一些时间来执行操作.
这是我们的测试代码:
- fun main(args: Array<String>) {
- val lazyValue by lazy(LazyThreadSafetyMode.PUBLICATION) {
- println("computation")
- BigInteger.valueOf(2).modPow(
- BigInteger.valueOf(7),
- BigInteger.valueOf(20)
- )
- }
- CacheThread(lazyValue).start()
- NetworkThread(lazyValue).start()
- }
结果如下:
- computation
- CacheThread 8
- NetworkThread 8
我们可以发现, 值只被计算了一次, 当 CacheThread 引用了 lazyValue 之后, 结果就被缓存了下来, 后面线程再访问都是访问的这个缓存的值, 不会再重新计算了.
它是怎么做的呢:
- private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
- @Volatile private var initializer: (() -> T)? = initializer
- @Volatile private var _value: Any? = UNINITIALIZED_VALUE
- // this final field is required to enable safe publication of constructed instance
- private val final: Any = UNINITIALIZED_VALUE
- override val value: T
- get() {
- val value = _value
- if (value !== UNINITIALIZED_VALUE) {
- @Suppress("UNCHECKED_CAST")
- return value as T
- }
- val initializerValue = initializer
- // if we see null in initializer here, it means that the value is already set by another thread
- if (initializerValue != null) {
- val newValue = initializerValue()
- if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
- initializer = null
- return newValue
- }
- }
- @Suppress("UNCHECKED_CAST")
- return _value as T
- }
- override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
- override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value)
- companion object {
- private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
- SafePublicationLazyImpl::class.java,
- Any::class.java,
- "_value"
- )
- }
- }
我们看到这里在调用了 initializer 之后就把就把它置为空了, 确保它只执行一次, 然后使用 java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater 来更新_value 的值, 这就保证了第一次计算出的结果会被成功保存下来.
好了, 到这里我们算是弄清楚三种模式的行为了, 知道它们用什么策略来获取一个结果, 接下来就要找哪里用到了这三个类的 value 字段, 把这个 value 返回给我们的委托对象的.
鉴于我之前在文章里都有告诉大家编译器会悄咪咪帮我们做事, 减少我们的工作量, 我猜这次也不例外, 还是写个最简单的例子, 从字节码入手:
- fun main() {
- val lazyValue by lazy { 1 }
- print(lazyValue)
- }
主要看字节码:
- // access flags 0x11
- public final main()V
- L0
- LINENUMBER 3 L0
- GETSTATIC Main$main$lazyValue$2.INSTANCE : LMain$main$lazyValue$2;
- CHECKCAST kotlin/jvm/functions/Function0
- INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
- GETSTATIC Main.$$delegatedProperties : [Lkotlin/reflect/KProperty;
- ICONST_0
- AALOAD
- ASTORE 2
- ASTORE 1
- L1
- LINENUMBER 4 L1
- ALOAD 1
- ASTORE 3
- ACONST_NULL
- ASTORE 4
- L2
- ALOAD 3
- INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf)
- L3
- CHECKCAST java/lang/Number
- INVOKEVIRTUAL java/lang/Number.intValue ()I
- ISTORE 3
- L4
- LINENUMBER 4 L4
- L5
- GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
- ILOAD 3
- INVOKEVIRTUAL java/io/PrintStream.print (I)V
- L6
- L7
- LINENUMBER 5 L7
- RETURN
- L8
- LOCALVARIABLE lazyValue Lkotlin/Lazy; L1 L8 1
- LOCALVARIABLE this LMain; L0 L8 0
- MAXSTACK = 3
- MAXLOCALS = 5
我们可以看到我们在打印时调用了 Lazy 的 getValue 方法.
我们就来找一找它, 很巧, 在这个枚举类上面, 相同的文件下(Lazy.kt), 包含了一个叫 getValue 的扩展方法:
- /**
- * An extension to delegate a read-only property of type [T] to an instance of [Lazy].
- *
- * This extension allows to use instances of Lazy for property delegation:
- * `val property: String by lazy { initializer }`
- */
- @kotlin.internal.InlineOnly
- public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
虽然这个方法看起来很奇怪, 不过这下就很明了了, 我们使用属性的时候是调用了这个方法, 它直接使用了 Lazy 接口的 value 值, 也就是我们刚刚分析的三个类中产生的 value 值, 注意, 它被 operator 修饰了, 这代表着我们不一定要通过方法名来调用它.
结合前面的源码分析, 我们可以稍做总结, Lazy 对象确实做到了惰性求值, 在我们访问属性, 间接调用了 getValue 方法的时候才根据有无缓存的值来判断是否要计算结果.
目前看来, 这里就是奥妙所在, 而且从注释来看跟 by 关键字配合起来实现的黑魔法. 按照套路总得有个规范是实现特定的功能, 我们目前了解的公共的东西怕是也只有 Lazy 接口了, 那 kotlin 是靠 Lazy 接口来创建代理属性的吗? 再继续追究下去, 我们就得先说说如何创建一个代理属性了.
一般来说, 对于一个用 val 声明的属性, 需要一个包含 get 方法的代理, 而对于用 var 声明的, 则 get,set 都需要有, 根据文档我们要实现 ReadWriteProperty 或者 ReadOnlyProperty 接口, 认真看的同学可能要问了, 不对呀, 我们刚刚看的 Lazy 系列都没有实现这些接口呀, 怎么能够实现代理功能的? 别急, 即将揭晓, 我们往下看:
- /**
- * Base interface that can be used for implementing property delegates of read-only properties.
- *
- * This is provided only for convenience; you don't have to extend this interface
- * as long as your property delegate has methods with the same signatures.
- *
- * @param R the type of object which owns the delegated property.
- * @param T the type of the property value.
- */
- public interface ReadOnlyProperty<in R, out T> {
- /**
- * Returns the value of the property for the given object.
- * @param thisRef the object for which the value is requested.
- * @param property the metadata for the property.
- * @return the property value.
- */
- public operator fun getValue(thisRef: R, property: KProperty<*>): T
- }
- /**
- * Base interface that can be used for implementing property delegates of read-write properties.
- *
- * This is provided only for convenience; you don't have to extend this interface
- * as long as your property delegate has methods with the same signatures.
- *
- * @param R the type of object which owns the delegated property.
- * @param T the type of the property value.
- */
- public interface ReadWriteProperty<in R, T> {
- /**
- * Returns the value of the property for the given object.
- * @param thisRef the object for which the value is requested.
- * @param property the metadata for the property.
- * @return the property value.
- */
- public operator fun getValue(thisRef: R, property: KProperty<*>): T
- /**
- * Sets the value of the property for the given object.
- * @param thisRef the object for which the value is requested.
- * @param property the metadata for the property.
- * @param value the value to set.
- */
- public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
- }
很巧, 都包含了一个跟前面我们找到的 Lazy 相似的 getValue 方法, 但是稍微看一下注释就发现其实并不是巧合, 接口不是必须的, 只要我们的类包含跟这些接口中的方法相同签名的方法, 就可以实现属性代理的功能, 那这样说我们也就豁然开朗了, 怪不得要给我们的 Lazy 接口增加一个签名这么奇怪的扩展方法, 怪不得 Lazy 的子类都能用作属性代理.
我是觉得实现接口可以避免我们方法签名写错, 毕竟这方法又长又奇怪, 而且实现起来也很简单:
- class MyDelegate<T> : ReadWriteProperty<Any?, T?> {
- private var value: T? = null
- override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
- value
- }
- override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
- this.value = value
- }
- }
这里我们直接返回了 vaue, 根据业务逻辑需求也可以在这里放复杂的逻辑.
使用起来就更简单了:
- fun main(args: Array<String>) {
- val value by MyDelegate<String>()
- println(value)
- }
到这里疑惑就都解开了, 只要有一个符合特定条件的对象, 这个对象的类不一定要实现特定的接口, 只要包含了那些签名特殊的 getValue, 或者 getValue,setValue 方法都有, 哪怕只是把这些方法声明成扩展方法也可以, 那这个对象就能作为代理属性在 by 关键字后面使用.
另外, 即使是局部变量也是可以使用代理属性的, 不过需要注意的是, 如果我们的代理会被局部变量使用, 那第一个泛型参数要是可以为空的(Nullable), 为什么呢, 我们来看一下反编译的 Java 代码:
- public final class MyDelegate implements ReadWriteProperty {
- private Object value; @Nullable
- public Object getValue(@Nullable Object thisRef, @NotNull KProperty property) {
- Intrinsics.checkParameterIsNotNull(property, "property");
- Object var10000 = this.value;
- return Unit.INSTANCE;
- }
- public void setValue(@Nullable Object thisRef, @NotNull KProperty property, @Nullable Object value) {
- Intrinsics.checkParameterIsNotNull(property, "property");
- this.value = value;
- }
- }
- public final void main(@NotNull String[] args) {
- Intrinsics.checkParameterIsNotNull(args, "args");
- MyDelegate var10000 = new MyDelegate();
- KProperty var3 = $$delegatedProperties[0];
- MyDelegate value = var10000;
- Object var4 = value.getValue((Object)null, var3);
- System.out.println(var4);
- }
我们发现对于本地变量 value,getValue 的第一个参数传的是 null, 因为本地变量不属于任何对象.
如果确定我们的代理只会被类的属性使用, 那么我们就可以直接把第一个泛型参数传为不可空(NonNull).
还没完, 按照我之前讨论类代理的套路, 我是要扒一扒使用多个代理的开销的, 再来看一个例子, 再添加一个使用相同代理的属性,
- class Main {
- val value by MyDelegate<String>()
- val value1 by MyDelegate<String>()
- }
这是反编译的 java 代码:
- public final class Main {
- // $FF: synthetic field
- static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Main.class), "value", "getValue()Ljava/lang/String;")), (KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Main.class), "value1", "getValue1()Ljava/lang/String;"))};
- @Nullable
- private final MyDelegate value$delegate = new MyDelegate();
- @Nullable
- private final MyDelegate value1$delegate = new MyDelegate(); @Nullable
- public final String getValue() {
- return (String)this.value$delegate.getValue(this, $$delegatedProperties[0]);
- }
- @Nullable
- public final String getValue1() {
- return (String)this.value1$delegate.getValue(this, $$delegatedProperties[1]);
- }
- }
我们可以看到, 跟之前讲类代理的时候一样, 每次使用代理都会单独创建一个代理对象, 在这儿显然不是必须的, 大家要有意识地减少开销, 我们可以按照老套路把它声明成一个单例, 至于如何声明也跟之前类代理的解决办法类似, 这里就不再赘述了.
此外我还发现一个有意思的东西, 我们的代理是支持泛型的, 这意味着它可以用于任意类, 比如这样:
- class Main {
- val value by MyDelegate<Int>()
- val value1 by MyDelegate<Float>()
- }
反编译成 Java 代码是这样的:
- public final class Main {
- // $FF: synthetic field
- static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Main.class), "value", "getValue()Ljava/lang/Integer;")), (KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Main.class), "value1", "getValue1()Ljava/lang/Float;"))};
- @Nullable
- private final MyDelegate value$delegate = new MyDelegate();
- @Nullable
- private final MyDelegate value1$delegate = new MyDelegate();
- @Nullable
- public final Integer getValue() {
- return (Integer)this.value$delegate.getValue(this, $$delegatedProperties[0]);
- }
- @Nullable
- public final Float getValue1() {
- return (Float)this.value1$delegate.getValue(this, $$delegatedProperties[1]);
- }
- }
做了一些类型转换, 这也是有开销的, 而我在之前分析 lambda 的时候翻到过一个文件 Ref.java, 里面单独给原始类型创建了类, 给其他类才提供了泛型版本:
- public static final class ObjectRef<T> implements Serializable {
- public T element;
- @Override
- public String toString() {
- return String.valueOf(element);
- }
- }
- public static final class ByteRef implements Serializable {
- public byte element;
- @Override
- public String toString() {
- return String.valueOf(element);
- }
- }
- public static final class ShortRef implements Serializable {
- public short element;
- @Override
- public String toString() {
- return String.valueOf(element);
- }
- }
库作者为了避免类型转换带来的开销, 特地加了这几个看起来冗余的类, 我们这里也是可以效仿一下的嘛:
- class IntDelegate : ReadOnlyProperty<Any?, Int?> {
- override fun getValue(thisRef: Any?, property: KProperty<*>): Int? {
- TODO()
- }
- }
总结
好了, 经过这么一通硬核的分析, 代理属性还能难得了谁? 还是那句话哈, 不一定是 Kotlin 比 Java 慢, 可能是我们写的代码姿势不对优化不到位, 大家平时学习的时候可以翻一翻源码, 多看看字节码, 多看看反编译的 Java 文件, 比较比较, 就能知道编译器为我们做了什么, 即能加深对这些语法糖的理解, 也能学到一些编码技巧.
免费获取更多安卓开发架构的资料 (包括 Fultter, 高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发(ReactNative+Weex) 和一线互联网公司关于 Android 面试的题目汇总可以加入[腾讯 @安卓中高级进阶]
来源: http://www.jianshu.com/p/0954ba184301