Class delegate
Class delegate actually corresponds to the proxy pattern in Java
interface Base{ fun text() } //Delegated class (real class) class BaseImpl(val x :String ) : Base{ override fun text() { println(x) } } //Principal (agent) class Devices (b :Base) :Base by b fun main(){ var b = BaseImpl("I am a real class") Devices(b).text() }
output
I am a real class
You can see that the delegate class (proxy class) holds the object of the real class, and then the delegate class (proxy class) calls the method with the same name of the real class. Finally, the real class that really implements the method is the real class, which is actually the proxy mode
The delegate implementation in kotlin relies on the by keyword. The by keyword is followed by the delegate class, which can be an expression
Decompile into java code
public final class Devices implements Base { // $FF: synthetic field private final Base $$delegate_0; public Devices(@NotNull Base b) { Intrinsics.checkNotNullParameter(b, "b"); super(); this.$$delegate_0 = b; } public void text() { this.$$delegate_0.text(); } } public final class BaseImpl implements Base { @NotNull private final String x; public void text() { String var1 = this.x; boolean var2 = false; System.out.println(var1); } @NotNull public final String getX() { return this.x; } public BaseImpl(@NotNull String x) { Intrinsics.checkNotNullParameter(x, "x"); super(); this.x = x; } } public interface Base { void text(); } public final class BaseKt { public static final void main() { BaseImpl b = new BaseImpl("I am a real class"); (new Devices((Base)b)).text(); } // $FF: synthetic method public static void main(String[] var0) { main(); } }
You can see that it is actually the proxy mode. Devices holds the BaseImpl object, rewrites the text method, and calls BaseImpl.text() internally
Attribute delegate
Property delegation is the same as class delegation. Property delegation is actually a delegation to the set/get method of the property, which delegates the set/get method to the setValue/getValue method. Therefore, the delegated class (real class) needs to provide setValue/getValue method, and the val property only needs to provide setValue method
Attribute delegate syntax:
Val / var < attribute name >: < type > by < expression >
class B { //Delegate properties var a : String by Text() } //Delegated class (real class) class Text { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "Property owner = $thisRef, The name of the property = '${property.name}' The value of the property " } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("The value of the property = $value The name of the property = '${property.name}' Property owner = $thisRef") } } fun main(){ var b = B() println(b.a) b.a = "ahaha" }
output
Property owner = com.example.lib.weituo.B@27fa135a, The name of the property = 'a' The value of the property The value of the property = ahaha The name of the property = 'a' Property owner = com.example.lib.weituo.B@27fa135a
As can be seen from the above example, attribute a is delegated to Text, and there are setValue and getValue in the Text class. Therefore, when we call the get/set method of attribute a, it will be delegated to setValue/getValue of Text. As can be seen from the above example, there are several parameters to introduce:
thisRef: the owner of the attribute
Property: the description of the property, which is kproperty < * > type or parent class
Value: the value of the attribute
Decompile into Java code
public final class B { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(B.class, "a", "getA()Ljava/lang/String;", 0))}; @NotNull private final Text a$delegate = new Text(); @NotNull public final String getA() { return this.a$delegate.getValue(this, $$delegatedProperties[0]); } public final void setA(@NotNull String var1) { Intrinsics.checkNotNullParameter(var1, "<set-?>"); this.a$delegate.setValue(this, $$delegatedProperties[0], var1); } } public final class BKt { public static final void main() { B b = new B(); String var1 = b.getA(); boolean var2 = false; System.out.println(var1); b.setA("ahaha"); } // $FF: synthetic method public static void main(String[] var0) { main(); } } public final class Text { @NotNull public final String getValue(@Nullable Object thisRef, @NotNull KProperty property) { Intrinsics.checkNotNullParameter(property, "property"); return "Property owner = " + thisRef + ", The name of the property = '" + property.getName() + "' The value of the property "; } public final void setValue(@Nullable Object thisRef, @NotNull KProperty property, @NotNull String value) { Intrinsics.checkNotNullParameter(property, "property"); Intrinsics.checkNotNullParameter(value, "value"); String var4 = "The value of the property = " + value + " The name of the property = '" + property.getName() + "' Property owner = " + thisRef; boolean var5 = false; System.out.println(var4); } }
You can see that class B holds a Text object. When calling the B.get() method, Text. GetValue () is called internally, and KProperty is created in B to save various parameters of the property
Simpler implementation of attribute delegation
Each time we implement a delegate, we need to write getValue/setValue methods, which is troublesome. The system provides us with an interface to rewrite these methods, ReadOnlyProperty and ReadWriteProperty
interface ReadOnlyProperty<in R, out T> { operator fun getValue(thisRef: R, property: KProperty<*>): T } interface ReadWriteProperty<in R, T> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
The delegated class only needs to implement the interface rewrite method, and val inherits ReadOnlyProperty
class Text1 : ReadOnlyProperty<Any, String> { override fun getValue(thisRef: Any, property: KProperty<*>): String { return "Property owner = $thisRef, The name of the property = '${property.name}' The value of the property " } } class Text2 : ReadWriteProperty<Any, String> { override fun getValue(thisRef: Any, property: KProperty<*>): String { return "Property owner = $thisRef, The name of the property = '${property.name}' The value of the property " } override fun setValue(thisRef: Any, property: KProperty<*>, value: String) { println("The value of the property = $value The name of the property = '${property.name}' Property owner = $thisRef") } } class B { val b :String by Text1() val c :String by Text2() }
Several delegates provided in the Koltin standard library
- Delay properties: its value is calculated only when accessing
- observable properties: the listener will receive the change notification of this property
- Map multiple attributes into a (Map) instead of having a single field
Delay attribute lazy
lazy() receives a lambda and returns a lazy instance. The returned instance can be used as a delegate to implement the delay attribute. It is initialized only when the attribute is called for the first time
class Lazy { val name :String by lazy { println("First call initialization") "aa" } } fun main(){ var lazy =Lazy() println(lazy.name) println(lazy.name) println(lazy.name) }
output
First call initialization aa aa aa
Decompile Java code
public final class Lazy { @NotNull private final kotlin.Lazy name$delegate; @NotNull public final String getName() { kotlin.Lazy var1 = this.name$delegate; Object var3 = null; boolean var4 = false; return (String)var1.getValue(); } public Lazy() { this.name$delegate = kotlin.LazyKt.lazy((Function0)null.INSTANCE); } } // LazyKt.java public final class LazyKt { public static final void main() { Lazy lazy = new Lazy(); String var1 = lazy.getName(); boolean var2 = false; System.out.println(var1); var1 = lazy.getName(); var2 = false; System.out.println(var1); var1 = lazy.getName(); var2 = false; System.out.println(var1); } // $FF: synthetic method public static void main(String[] var0) { main(); } }
You can see that the name$delegate variable generated during the reinitialization of lazy is of type kotlin.Lazy, and the value returned by the getName() method is actually name$delegate.getValue()
name$delegate is generated by kotlin.LazyKt.lazy((Function0)null.INSTANCE). Let's look at the source code
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Finally, it is generated by synchronized lazyimpl. Continue to follow the source code
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) }
We can directly look at the get method of value if_ v1 !== UNINITIALIZED_VALUE indicates that it has been initialized and returns value directly. Otherwise, it indicates that it has not been initialized. Call the initializer method, that is, the lambda expression of lazy
In fact, it is similar to the delegate implemented by yourself. It also implements the getValue method
lazy delegate parameters
public enum class LazyThreadSafetyMode { /** * Locks are used to ensure that only a single thread can initialize the [Lazy] instance. */ SYNCHRONIZED, /** * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, * but only the first returned value will be used as the value of [Lazy] instance. */ PUBLICATION, /** * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread. */ NONE, }
- SYNCHRONIZED: add synchronization lock to make lazy delay initialization thread safe
- PUBLICATION: the initialized lambda expression can be called multiple times at the same time, but only the first return value is used as the initialization value
- NONE: no synchronization lock, non thread safe
val name :String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { println("First call initialization") "aa" } }
Observable attribute observable delegate
You can observe the change process of an attribute
class Lazy { var a : String by Delegates.observable("Default value"){ property, oldValue, newValue -> println( "$oldValue -> $newValue ") } } fun main(){ var lazy =Lazy() lazy.a = "First modification" lazy.a = "Second modification" }
output
Default value -> First modification First modification -> Second modification
vetoable delegate
Like Observable, vetoable can observe the changes of attributes. The difference is that vetoable can decide whether to use new values
class C { var age: Int by Delegates.vetoable(0) { property, oldValue, newValue -> println("oldValue = $oldValue -> oldValue = $newValue" ) newValue > oldValue } } fun main() { var c = C() c.age = 5 println(c.age) c.age = 10 println(c.age) c.age = 8 println(c.age) c.age = 20 println(c.age) }
output
oldValue = 0 -> oldValue = 5 5 oldValue = 5 -> oldValue = 10 10 oldValue = 10 -> oldValue = 8 10 oldValue = 10 -> oldValue = 20 20
When the new value is less than the old value, it will not take effect. You can see that the value set for the third time is 8, and if it is less than 10, it will not take effect
Properties are stored in the Map
class D(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main(){ var d = D(mapOf( "name" to "Xiao Ming", "age" to 12 )) println("name = ${d.name},age = ${d.age}") }
output
name = Xiao Ming,age = 12