委托可以帮助您将任务委托给其他对象,并提供更好的代码重用,您可以在如下文章中了解更多相关内容。
Kotlin不仅支持一种通过关键字实现委托的简单方法,而且还在Kotlin标准库中提供了内置委托,如lazy()、observable()、vetoable()和notull()。让我们看看这些内置委托以及它们在底层是如何工作的。
1、lazy()
lazy()
:惰性求值委托
lazy()函数是一个属性委托,它帮助您惰性地初始化属性,也就是当它们第一次被访问时。Lazy()对于创建成本较高的对象非常有用。
lazy()
接受两个参数,LazyThreadSafetyMode
枚举值和lambda。
LazyThreadSafetyMode
参数指定如何在不同线程之间同步初始化,lazy()
默认值使用LazyThreadSafetyMode.SYNCHRONIZED
,这意味着初始化是线程安全的,代价是显式同步对性能的轻微影响。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
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)
}
在第一次访问属性时执行lambda,然后存储它的值以供将来访问。
class Product(price: Double, oldPrice: Double) {
val plummetPrice :Double by lazy {
oldPrice - price
}
}
底层实现
在检查反编译的Java代码时,我们看到Kotlin编译器为Lazy委托创建了一个Lazy
类型的引用。
@NotNull
private final Lazy plummetPrice$delegate;
这个委托是由LazyKt.lazy()
函数使用您指定的lambda和线程安全模式参数初始化的。
this.plummetPrice$delegate = LazyKt.lazy((Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
public final double invoke() {
return oldPrice - price;
}
}));
让我们看一看lazy()的源代码。因为lazy()函数使用的默认值是LazyThreadSafetyMode.SYNCHRONIZED返回一个SynchronizedLazyImpl类类型的Lazy对象。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
当委托的属性第一次被访问时,SynchronizedLazyImpl的getValue()函数将被调用,它将初始化同步块中的属性。
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
}
}
}
这保证了惰性对象以线程安全的方式初始化,但增加了同步块的性能成本。
注意:如果你确定这个资源将由一个线程初始化,你可以传递
LazyThreadSafetyMode.NONE
到lazy()
,函数在惰性初始化时不会使用synchronized
。但是,请记住LazyThreadSafetyMode.NONE
不会改变惰性初始化的同步性质。由于惰性初始化是同步的,因此在第一次访问时,它所花费的时间与非惰性初始化对象所花费的时间相同。如果访问它,这意味着需要很长时间初始化的对象仍然可以阻塞UI线程。
val lazyValue: String by lazy(LazyThreadSafetyMode.NONE) {" lazy "}
2、observable
observable
:可观察属性委托 用于在属性值变化时自动调用指定的回调函数
delegate .observable()
是Kotlin标准库的另一个内置委托。观察者是一种设计模式,在这种模式中,对象维护其依赖项列表(称为观察者),并在其状态更改时自动通知它们。当一个值发生变化时,需要通知多个对象,而不是让每个依赖对象定期调用并检查资源是否更新时,这种模式非常有用。
Observable()
接受两个参数:初始值和一个侦听器,当值被修改时将被调用.observable()
创建一个ObservableProperty
对象,在每次调用setter时执行你传递给委托的lambda。
class Product{
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
name = "苹果" // 输出 "<no name> -> 苹果"
name = "香蕉" // 输出 "苹果 -> 香蕉"
}
查看反编译的Product类,我们看到Kotlin编译器生成了一个扩展ObservableProperty的类。该类还实现了一个名为afterChange()的函数,该函数具有传递给可观察委托的lambda函数。
protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
Intrinsics.checkNotNullParameter(property, "property");
String var4 = (String)newValue;
String old = (String)oldValue;
int var7 = false;
String var8 = old + " -> " + var4;
System.out.println(var8);
}
afterChange()函数由父类ObservableProperty的setter调用。这意味着每当调用方为name设置一个新值时,setter将自动调用afterChange()函数,这样所有侦听器都收到关于更改的通知。
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
您还可以在反编译代码中看到对beforeChange()的调用。beforeChange()不是被可观察委托使用的,而是下一个将要出现的委托vetoable()做参考。
3、vetoable
vetoable
:可否决属性委托 用于在属性值变化时检查新值是否合法,如果不合法则回滚到旧值。
Vetoable()是另一个内置委托,其中属性将否决权委托给其值。与observable()委托类似,vetoable()接受两个参数:初始值和监听器,当任何调用者想要修改属性的值时将调用该监听器。
var age: Int by Delegates.vetoable(0) {
prop, old, new ->
if (new >= 0) {
true // 新值合法,允许更新
} else {
false // 新值不合法,回滚到旧值
}
}
age = 20
println(age) // 输出 "20"
age = -10
println(age) // 输出 "20",因为新值不合法被否决了
如果lambda条件返回true,属性值将被修改,否则值将保持不变。在如上情况下,如果调用方试图更新小于0,则将保留当前值。
看一下反编译的类,Kotlin生成了一个扩展ObservableProperty的新类。生成的类包括我们在beforeChange()函数中传递的lambda,它将在值设置之前由setter调用。
public final class Person$$special$$inlined$vetoable$1 extends ObservableProperty {
public Person$$special$$inlined$vetoable$1(Object $initialValue) {
super($initialValue);
}
protected boolean beforeChange(@NotNull KProperty property, Object oldValue, Object newValue) {
Intrinsics.checkNotNullParameter(property, "property");
int var4 = ((Number)newValue).intValue();
int old = ((Number)oldValue).intValue();
int var7 = false;
return var4 >= 0;
}
}
4、notNull
notNull:非空属性委托 用于确保属性在第一次访问前已经被初始化。如果在访问前未被初始化,则会抛出异常。
Kotlin标准库提供的最后一个内置委托是delegats.notull()。 notNull()只是允许属性在稍后进行初始化。notNull()类似于lateinit。在大多数情况下,lateinit是首选,因为notNull()会为每个属性创建一个额外的对象。但是,可以对原始类型使用notNull(), lateinit不支持这种类型。
val fullname: String by Delegates.notNull<String>()
notNull()使用一种特殊类型的ReadWriteProperty,名为nottnullvar。
查看反编译的代码,fullname属性是用nottnull()函数初始化的。
this.fullname$delegate = Delegates.INSTANCE.notNull()
该函数返回一个nottnullvar对象。
public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
nottnullvar类只是持有一个泛型的可空的内部引用,如果任何代码在值初始化之前调用getter,则抛出IllegalStateException()。
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 内置了一些属性委托(Delegate)类,它们是:
lazy
:惰性求值委托,用于将属性的初始化推迟到第一次访问时。observable
:可观察属性委托,用于在属性值变化时自动调用指定的回调函数vetoable
:可否决属性委托,用于在属性值变化时检查新值是否合法,如果不合法则回滚到旧值。notNull
:非空属性委托,用于确保属性在第一次访问前已经被初始化。如果在访问前未被初始化,则会抛出异常。