Kotlin Vocabulary | Kotlin 内建代理

Kotlin Vocabulary | Kotlin 内建代理

代理可以帮助您将任务委托给其他对象,从而带来更佳的代码复用性,您可以从 我们之前的文章 了解到更多信息。Kotlin 不仅可以让您通过 by 关键字轻松实现代理,还在标准库中提供了像 lazy()、observable()、vetoable() 以及 notNull() 这样的内建代理。接下来就让我们开始了解这些内建代理的使用,以及其内部的实现原理。

lazy()

lazy() 函数是一个属性代理,它可以帮您在第一次访问属性时对它们进行惰性初始化。这个函数在创建昂贵对象时十分有用。

lazy() 函数接收两个参数,LazyThreadSafetyMode 枚举值与一个 lambda 表达式。

LazyThreadSafetyMode 用于指定初始化过程如何在不同线程间进行同步,它的默认值是 LazyThreadSafetyMode.SYNCHRONIZED。这意味着初始化操作是线程安全的,但代价是显式同步会对性能造成轻微影响。

lambda 表达式会在属性第一次被访问时执行,而它的值将会被存储以用于接下来的访问。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person(name: String, lastname: String) {
   val fullname: String by lazy() {
     name + lastname
   }
   //…
}

内部原理

在查看反编译后的 Java 代码时,我们可以看到 Kotlin 编译器为惰性 (lazy) 代理创建了一个 Lazy 类型的引用:

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

@NotNull
private final Lazy fullname$delegate;

这一代理通过调用 LazyKt.lazy() 函数,并传入您定义的 lambda 表达式与线程安全模式参数来进行初始化:

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

  this.fullname$delegate = LazyKt.lazy((Function0)(new Function0() {
     @NotNull
     public final String invoke() {
        return name + lastname;
     }
  }));

让我们来观察 lazy() 的源码。由于 lazy() 函数默认使用 LazyThreadSafetyMode.SYNCHRONIZED 参数,因此它将返回一个 SynchronizedLazyImpl 类型的 Lazy 对象:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
   SynchronizedLazyImpl(initializer)

当属性代理第一次被访问时,SynchronizedLazyImplgetValue() 函数就会被调用,这个函数将会在一个 synchronized 块中对属性进行初始化:

 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
           }
       }
   }

这样就能保证惰性对象会以线程安全的方式初始化,但同时也引入了由 synchronized 块带来的额外开销。

注意: 如果您确定资源会在单线程中被初始化,您可以向 lazy() 传入 LazyThreadSafetyMode.NONE,这样函数就不会在惰性初始化时使用 synchronized 块。不过请记住,LazyThreadSafetyMode.NONE 不会改变惰性初始化的同步特性。由于惰性初始化是同步的,所以在第一次访问时仍会消耗与非惰性初始化过程相同的时间,这意味着那些初始化过程较为耗时的对象仍会在被访问时阻塞 UI 线程。

1val lazyValue: String by lazy(LazyThreadSafetyMode.NONE) {“lazy”}

惰性初始化可以帮助初始化昂贵资源,但对于诸如 String 一类的简单对象,由于 lazy() 函数需要生成 LazyKProperty 这样的额外对象,反而会增加整个过程的开销。

Observable

Delegates.observable() 是另一个 Kotlin 标准库中内建的代理。观察者模式是一种设计模式,在这一模式中,一个对象会维护一个它的从属者的列表,这些从属者即被称为观察者。对象会在它自己的状态改变时对观察者进行通知。这一模式十分契合多个对象需要在某个值发生改变时得到通知的情况,可以避免实现为从属对象周期调用和检查资源是否更新。

observable() 函数接收两个参数: 初始化值与一个当值发生改变时会被调用的监听处理器。observable() 会创建一个 ObservableProperty 对象,用于在每次 setter 被调用时执行您传给代理的 lambda 表达式。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person {
    var address: String by Delegates.observable("not entered yet!") {
        property, oldValue, newValue ->
        // 执行更新操作
    }
}

通过观察反编译后的 Person 类型,我们可以看到 Kotlin 编译器生成了一个继承 ObservableProperty 的类。这个类同时也实现了一个叫做 afterChange() 的函数,这个函数持有您传递给 observable 代理的 lambda 函数。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
               // 执行更新操作
}

afterChange() 函数由父 ObservableProperty 类的 setter 调用,这意味着每当调用者为 address 设置一个新的值,setter 就会自动调用 afterChange() 函数,结果就会使所有的监听器都会收到有关改变的通知。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person {
    var address: String by Delegates.observable("not entered yet!") {
        property, oldValue, newValue ->
        // 执行更新操作
    }
}

您还可以从反编译后的代码中看到 beforeChange() 函数的调用,observable 代理不会使用 beforeChange()。但对于您接下来会看到的 vetoable(),这一函数却是功能实现的基础。

vetoable

vetoable() 是一个内建代理,属性将否决权委托给它的值。与 observable() 代理类似,vetoable() 同样接受两个参数: 初始值与监听器,当任何调用者想要修改属性值时,监听器就会被调用。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

var address: String by Delegates.vetoable("") {
       property, oldValue, newValue ->
   newValue.length > 14
}

如果 lambda 表达式返回 true,属性值就会被修改,反之则保持不变。在本例中,如果调用者尝试使用长度小于 15 个字符的字符串来更新地址的话,当前值就不会发生改变。

观察反编译后的 Person 可以发现,Kotlin 新生成了一个继承 ObservableProperty 的类,该类中包含了我们传入 beforeChange() 函数的 lambda 表达式,setter 会在每次值被设置之前调用 lambda 表达式。

 <!-- Copyright 2020 Google LLC.
 SPDX-License-Identifier: Apache-2.0 -->
 
 public final class Person$$special$$inlined$vetoable$1 extends ObservableProperty {
 
   protected boolean beforeChange(@NotNull KProperty property, Object oldValue, Object newValue) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      String newValue = (String)newValue;
      String var10001 = (String)oldValue;
     int var7 = false;
     return newValue.length() > 14;
  }
}

notNull

Kotlin 标准库中所提供的最后一个内建代理是 Delegates.notNull()notNull() 允许一个属性可以延后一段时间初始化,与 lateinit 类似。由于 notNull() 会为每个属性创建额外的对象,所以大多数情况下推荐使用 lateinit。不过,您可以将 notNull() 与原生类型一同使用,这点是 lateinit 所不支持的。

val fullname: String by Delegates.notNull<String>()

notNull() 使用一种特殊类型的 ReadWriteProperty: NotNullVar。

我们可以查看反编译后的代码,下面的例子中使用 notNull() 函数初始化了 fullname 属性:

this.fullname$delegate = Delegates.INSTANCE.notNull();

该函数返回了一个 NotNullVar 对象:

public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

NotNullVar 类型持有一个泛型的可空内部引用,如果在初始化值之前有任何代码调用 getter,则抛出 IllegalStateException()

 <!-- Copyright 2020 Google LLC.
 SPDX-License-Identifier: Apache-2.0 -->
 
 private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null
 
    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

   public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
       this.value = value
   }
}

有了 Kotlin 标准库提供的这组内建代理,您无需再编写、维护与重新发明这些功能。这些内建代理可以帮您惰性初始化字段、允许原生类型延迟加载、监听并在值发生改变时获得通知,甚至可以否决属性值更改。

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io Funder | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.
🍗