This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Commissioned by class

Class delegates actually correspond to the proxy pattern in Java

interface Base{
    fun text(a)
}

// Delegate class (real class)
class BaseImpl(val x :String ) : Base{
    override fun text(a) {
        println(x)
    }
}

// Delegate class
class Devices (b :Base) :Base by b


fun main(a){
    var b = BaseImpl("I am the real class.")
    Devices(b).text()
}
Copy the code

The output

I am real classCopy the code

As you can see, the delegate class (proxy class) holds the object of the real class, then the delegate class (proxy class) calls the method of the same name of the real class, and finally really implements the method of the real class. This is actually the proxy pattern

The delegate implementation in Kotlin uses the by keyword, which is followed by the delegate class, which can be an expression

Decompile into Java code and let’s see

public final class Devices implements Base {
   // $FF: synthetic field
   private final Base $$delegate_0;

   public Devices(@NotNull Base b) {
      Intrinsics.checkNotNullParameter(b, "b");
      super(a);this.$$delegate_0 = b;
   }

   public void text(a) {
      this.$$delegate_0.text(); }}public final class BaseImpl implements Base {
   @NotNull
   private final String x;

   public void text(a) {
      String var1 = this.x;
      boolean var2 = false;
      System.out.println(var1);
   }

   @NotNull
   public final String getX(a) {
      return this.x;
   }

   public BaseImpl(@NotNull String x) {
      Intrinsics.checkNotNullParameter(x, "x");
      super(a);this.x = x; }}public interface Base {
   void text(a);
}


public final class BaseKt {
   public static final void main(a) {
      BaseImpl b = new BaseImpl("I am the real class.");
      (new Devices((Base)b)).text();
   }

   // $FF: synthetic method
   public static void main(String[] var0) { main(); }}Copy the code

Devices hold the BaseImpl object and override the text method, which internally calls baseimp.text ().

Attribute to entrust

The set/ GET method is delegated to setValue/getValue. Therefore, the delegated class (real class) needs to provide setValue/getValue methods. The val attribute only needs to provide the setValue method

Attribute delegate syntax:

Var < attribute name >: < type > by < expression >


class B {
    // Delegate properties
    var a : String by Text()
}

// Delegate class (real class)
class Text {
    operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
        return "Property owner =$thisRef, attribute name = '${property.name}'Property value'
    }

    operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String) {
        println("Property value =$valueAttribute name = '${property.name}'Property owner =$thisRef")}}fun main(a){
    var b = B()

    println(b.a)

    b.a = "ahaha"
    
}
Copy the code

The output

Property owner = com.example.lib.weituo.B@27fa135a, property name ='a'Attribute value Attribute value = ahaha attribute name ='a'Property owner = com.example.lib.weituo.B@27fa135a
Copy the code

As you can see from the above example, property A is delegated to Text, and the Text class has setValue and getValue, so when we call property A’s get/set methods, Delegate to Text setValue/getValue, as shown in the example above.

ThisRef: Owner of the property

Property: A description of the property, which is of type KProperty<*> or its parent class

Value: Indicates the value of the attribute

Decompile into Java code and let’s see

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(a) {
      return this.a$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setA(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.a$delegate.setValue(this, $$delegatedProperties[0], var1); }}public final class BKt {
   public static final void main(a) {
      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 "Attribute Owner =" + thisRef + ", attribute name = '" + property.getName() + "Value of 'attribute';
   }

   public final void setValue(@Nullable Object thisRef, @NotNull KProperty property, @NotNull String value) {
      Intrinsics.checkNotNullParameter(property, "property");
      Intrinsics.checkNotNullParameter(value, "value");
      String var4 = "Attribute value =" + value + "Attribute name = '" + property.getName() + "' Attribute owner =" + thisRef;
      boolean var5 = false; System.out.println(var4); }}Copy the code

As you can see, class B holds the Text object. When the b.et () method is called, text.getValue () is internally called, and KProperty is created in B to hold the various parameters of the property

Simpler implementation of property delegates

We have to write getValue/setValue methods every time we implement a delegate, which is a bit of a hassle. We have interfaces to override 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)
}

Copy the code

The delegated class simply implements the interface override method, and Val inherits ReadOnlyProperty

class Text1 : ReadOnlyProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty< * >): String {
        return "Property owner =$thisRef, attribute name = '${property.name}'Property value'}}class Text2 : ReadWriteProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty< * >): String {
        return "Property owner =$thisRef, attribute name = '${property.name}'Property value'
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("Property value =$valueAttribute name = '${property.name}'Property owner =$thisRef")}}class B {

    val b :String by Text1()

    val c :String by Text2()

}
Copy the code

Several delegates provided in the Koltin standard library

  • Lazy properties: Its value is only evaluated at access time
  • Observable Properties: Listeners are notified of changes to this property
  • Map multiple attributes to (Map) instead of having a single field

The delay attribute lazy

Lazy () takes a lambda and returns an instance of lazy that can be used as a delegate to implement the delayed property, initialized only on the first call to the property

class Lazy {
    val name :String by lazy {
        println("First call initialization")
        "aa"}}fun main(a){
    var lazy =Lazy()
    println(lazy.name)
    println(lazy.name)
    println(lazy.name)
}
Copy the code

The output

Initial call aa AA AACopy the code

Decompile Java code

public final class Lazy {
   @NotNull
   private final kotlin.Lazy name$delegate;

   @NotNull
   public final String getName(a) {
      kotlin.Lazy var1 = this.name$delegate;
      Object var3 = null;
      boolean var4 = false;
      return (String)var1.getValue();
   }

   public Lazy(a) {
      this.name$delegate = kotlin.LazyKt.lazy((Function0)null.INSTANCE); }}// LazyKt.java

public final class LazyKt {
   public static final void main(a) {
      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(); }}Copy the code

$delegate (kotlin.lazy), and the getName() method returns name$delegate.getValue().

Name $delegate is generated by kotlin.lazykt.lazy ((Function0) null.instance)

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

Ultimately generated by SynchronizedLazyImpl, continue with 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 vallock = 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 {
                    valtypedValue = initializer!! () _value = typedValue initializer =null
                    typedValue
                }
            }
        }

    override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUEoverride fun toString(a): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(a): Any = InitializedLazyImpl(value)
}
Copy the code

We can look directly at the get method of value, if _v1! UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE

In fact, the same as their implementation of the delegate, also implement getValue method

Lazy delegate parameter

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,
}
Copy the code
  • SYNCHRONIZED: Adds a synchronization lock to make lazy lazy thread initialization safe
  • PUBLICATION: an initialized lambda expression that can be called multiple times at the same time, but only the return value of the first time is the initialized value
  • NONE: No synchronization lock, not thread safe
    val name :String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        println("First call initialization")
        "aa"}}Copy the code

Observable attribute Observable delegate

You can observe the evolution of a property

class Lazy {
   
    var a : String by Delegates.observable("Default value"){
            property, oldValue, newValue ->

        println( "$oldValue -> $newValue ")}}fun main(a){
    var lazy =Lazy()


    lazy.a = "First modification"

    lazy.a = "Second modification"
}
Copy the code

The output

Default value -> First Change First change -> Second changeCopy the code

Vetoable commissioned

Like an Observable, a Vetoable can observe changes in properties, except that a 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(a) {
    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)
}
Copy the code

The output


oldValue = 0 -> oldValue = 5
5
oldValue = 5 -> oldValue = 10
10
oldValue = 10 -> oldValue = 8
10
oldValue = 10 -> oldValue = 20
20
Copy the code

If the new value is less than the old value, then it does not take effect, and you can see that the third value is 8, and it does not take effect if it is less than 10

Properties are stored in the Map

class D(valmap: Map<String, Any? >) {val name: String by map
    val age: Int by map
}

fun main(a){
    var d = D(mapOf(
        "name" to "Xiao Ming"."age" to 12
    ))


    println("name = ${d.name},age = ${d.age}")}Copy the code

The output

Name = Xiaoming,age =12
Copy the code