Before the order

Delegate, for many Java developers, is a blind spot, and I’m no exception. Delegate, wikipedia says: Two objects participate in processing the same request, and the object receiving the request delegates the request to another object. It ‘seems to have a whiff of agency. In Kotlin, delegates are divided into class delegates and delegate attributes.

Commissioned by class

Before we can explain class delegates, we need to understand a wave of decorative design patterns. The core idea of decoration design mode is:

To extend the functionality of an object to make it more powerful without using inheritance.

The usual pattern is to create a new class that implements the same interface as the original class and stores an instance of the original class as a field with the same behavior (methods) as the original class. Some of the behaviors (methods) are consistent with the original class (that is, they call the behavior (methods) of the original class directly), and some of the behaviors (methods) extend the behavior (methods) of the original class.

The disadvantage of the decorative design pattern is that it requires more boilerplate code, which is more verbose. For example, the primitive decorator class needs to implement all the methods of the interface and call the corresponding methods of the primitive class object in those methods.

class CustomList<T>(
    val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> {
    
    override val size: Int = innerList.size
    override fun contains(element: T): Boolean  = innerList.contains(element)
    override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
    override fun isEmpty(): Boolean  = innerList.isEmpty()
    override fun add(element: T): Boolean  = innerList.add(element)
    override fun addAll(elements: Collection<T>): Boolean  = innerList.addAll(elements)
    override fun clear()  = innerList.clear()
    override fun iterator(): MutableIterator<T>  = innerList.iterator()
    override fun remove(element: T): Boolean  = innerList.remove(element)
    override fun removeAll(elements: Collection<T>): Boolean  = innerList.removeAll(elements)
    override fun retainAll(elements: Collection<T>): Boolean  = innerList.retainAll(elements)
    
}
Copy the code

But Kotlin has first-class support for delegation as a language-level feature. You can use the BY keyword to delegate the interface implementation of a new class to the original class. The compiler automatically generates interface methods for the new class and returns the corresponding concrete implementation of the original class by default. Then we override the methods that need to be extended.

class CustomList<T>(
    val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> by innerList{
    
    override fun add(element: T): Boolean {
        println("CustomList add element")
        innerList.add(element)
    }

}
Copy the code

Entrusted property

A delegate attribute delegates its accessors (GET and set) to an object that conforms to the rules of the attribute delegate convention.

Delegate properties are different from class delegates in that they are more like proxies for properties. Delegate properties also delegate properties to a proxy object using the BY keyword. Property proxy objects do not need to implement any interface, but need to provide a getValue() and setValue() function (var property only). Such as:

class Person{ var name:String by Delegate() } class Delegate { operator fun getValue(thisRef: Any? , property: KProperty<*>): String {return "kotlin"
    }
 
    operator fun setValue(thisRef: Any? , property: KProperty<*>, value: String){ } }Copy the code

The property name delegates its set/get methods to the Delegate object’s getValue() and setValue(). The operator modifier in both getValue() and setValue() means that delegate properties are also convention-dependent functions. Like other convention functions, getValue() and setValue() can be member functions or extension functions.

The ReadOnlyProperty or ReadWriteProperty interface is available in the official Kotlin library for developers to implement to provide the correct getValue() and setValue() methods.

public interface ReadOnlyProperty<in R, out T> {
    public operator fun getValue(thisRef: R, property: KProperty<*>): T
}

public interface ReadWriteProperty<in R, T> {
    public operator fun getValue(thisRef: R, property: KProperty<*>): T
    public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Copy the code

Using delegate properties

Lazy initialization

When you need to do lazy initialization of an attribute, you often think of using lateInit var for lazy initialization. But that’s for var variables, mutable variables, but what about val variables? Lazy initialization can be implemented using support attributes:

Class Person{private var _emails:List<Email>? If the emails are sent by val <Email> = nullget() {
            if ( _emails == null){
                _emails = ArrayList<Email>()
            }
            return_emails!! }}Copy the code

Provides one “hidden” property _emails to store the real value, and another property that provides read access to the property. The _emails variable is initialized and the _emails object is returned only when the emails are being accessed, delaying the initialization of the Val object.

But this scenario is verbose when multiple lazy attributes are required, and it is not thread-safe. Kotlin provides a more convenient solution: delegate properties and use the standard library function lazy to return the proxy object.

class Person{
    val email:List<Email> by lazy {
        ArrayList<Email>()
    }
}
Copy the code

The lazy function receives a lambda that initializes the value operation and returns a proxy object with a getValue() method, delegating the property to the proxy object returned by the lazy function along with the by keyword. Lazy functions are thread-safe and do not have to worry about asynchrony.

Notification of property changes

The most primitive way to be notified when an object’s properties need to change is to override the set method and set the logic in it to handle property changes. Manually implement notification of property changes:

class Person(name:String,age:Int){
    var age :Int = age
        set(newValue) {val oldValue = field field = newValue // Listen for value changes (or use Listener objects) valueChangeListener("age",oldValue,newValue)
        }
    var name :String = name
        set(newValue) {val oldValue = field field = newValue valueChangeListener("name",oldValue,newValue)
        }

    fun <T> valueChangeListener(fieldName:String,oldValue:T,newValue:T){
        println("$fieldName oldValue = $oldValue newValue = $newValue")}}Copy the code

But this scenario is similar to the original example of lazy initialization, where the code is verbose and verbose when you need to listen for multiple attributes. We can implement the delegate property as lazy initialization:

class Person(name:String,age:Int){ var age:Int by PropertyObservable(age){ property, oldValue, newValue -> } var name:String by PropertyObservable(name){ property, oldValue, // Class PropertyObservable<T>(var initValue:T, val Observer: property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Person, T> { override fun getValue(thisRef: Person, property: KProperty<*>): T {return initValue;
    }

    override fun setValue(thisRef: Person, property: KProperty<*>, newValue: T) {val oldeValue = initValue initValue = newValue / / monitored values change (or use the Listener object) observer (property, oldeValue, newValue)}}Copy the code

Define a delegate class that “takes over” the get/set of a delegate attribute and provides initial values and processing logic when the attribute is modified. Greatly simplifies the code for listening to property Settings.

But Kotlin has provided the Delegates.Observable () method in the standard library, which makes it much easier for us to monitor the modification of property using delegate property. Like our custom delegate class, the method receives the initialization value of property and processing logic when property changes:

class Person(name:String,age:Int){
    var age:Int by Delegates.observable(age){ property, oldValue, newValue ->  
        println("${property.name} oldValue = $oldValue newValue = $newValue")
    }
    var name:String by Delegates.observable(name){ property, oldValue, newValue ->
         println("${property.name} oldValue = $oldValue newValue = $newValue")}}Copy the code

In the standard library Kotlin provides a method similar to Delegates.Observable () :Delegates.vetoable(). But before the attribute is assigned new value, it will be transmitted to Delegates. Vetoable () for processing, according to the Boolean value returned by Delegates. Vetoable (), it will judge whether the new value is assigned.

The third type is delayed initialization

As you’ve seen before, you can use the lateInit keyword when the var attribute needs lazy initialization, and you can use the delegate attribute + lazy() method when the val attribute needs lazy initialization. But the lateInit keyword is only useful for reference types, not for basic data types. What happens when you need lazy initialization for basic data types? Kotlin provides another way of deferred initialization via the delegate attribute: Delegates.notnull ()

var num:Int by Delegates.notNull()
Copy the code

While Kotlin provides lazy initialization so that developers don’t have to force initialization in constructors (such as onCreate in an Activity), lazy initialization values must be guaranteed to be initialized otherwise they will throw an exception, just like Java null Pointers.

way Suitable type
lateinit Reference types
Delegates.notNull() Basic data type, reference type

Transformation rules

Behind each implementation of a delegate property, the Kotlin compiler generates and delegates a secondary property to it. For example, for the property name, the compiler generates a hidden property name$delegate, and the code for the property name accessor delegates to the hidden property getValue()/setValue().

class Person{
    var name:String by MyDelegate()
}
Copy the code

The compiler generates the following code:

class Person{
    private val name$delegate = MyDelegate()
    
    var name:String
        get() = name$delegate.getValue(this,<property>)
        set(value:String) = name$delegate.setValue(this,<property>,value)
}
Copy the code
  • ThisRef represents the object that holds the delegate property
  • Property A description of the KProperty<*> type or its parent, the property. (Names of available properties, etc.)
  • Value Indicates the new value of the property

The source code to read

Master how to use the Kotllin delegate attribute, but also need to understand the source code of the delegate attribute:

NotNullVar: Delegates) notNull () the delegate class lazy initialization

Delegates:Delegates declare themselves as an object with three very familiar methods: notNull(), Observable, and vetoable.

ObservableProperty: ObservableProperty Is a system-defined delegate class that observableObservables and Vetoable return anonymous objects to.

Delegates.notNull()

Delegates.notnull () returns the NotNullVar object directly as the delegate object of the delegate property.

#Delegates.ktpublic fun <T : Any> notNull(): ReadWriteProperty<Any? , T> = NotNullVar()Copy the code
#Delegates.ktprivate class NotNullVar<T : Any>() : ReadWriteProperty<Any? , T> { private var value: T? = null public override fun getValue(thisRef: Any? , property: KProperty<*>): T {returnvalue ? : throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

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

It can be seen from the source code that its internal implementation is the same principle as the previously used supporting attribute, but it provides getValue() and setValue() to Delegates the var attribute.

Delegates.observable()

Delegates. Observable (), like Delegates. Vetoable (), is an anonymous object that returns the ObservableProperty directly. But Delegates. Observable () overloads afterChange and executes the lambda received by afterChange. ObservableProperty#setValue() After assigning a new value to the property, the afterChange function is executed using the old and new values as arguments.

#Delegates.ktpublic inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any? , T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) }Copy the code
#ObservableProperty.kt
protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}

public override fun setValue(thisRef: Any? , property: KProperty<*>, value: T) { val oldValue = this.valueif(! beforeChange(property, oldValue, value)) {return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
Copy the code

Delegates.vetoable()

Delegates. Vetoable () is very similar to Delegates. Observable () except for the overloaded function, Delegates. Vetoable () overloads the beforeChange function. The getValue() of ObservableProperty obtains the return value of the beforeChange function (default is true) to determine whether to continue with the assignment. So it’s the difference between the Delegates. Vetoable ().

#Delegates.ktpublic inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty<Any? , T> = object : ObservableProperty<T>(initialValue) { override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue) }Copy the code
#ObservableProperty.kt
protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true

public override fun setValue(thisRef: Any? , property: KProperty<*>, value: T) {val oldValue = this.value // If beforeChange returnsfalse, returns the function directly without assigning a valueif(! beforeChange(property, oldValue, value)) {return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
Copy the code

Principle of delegate properties

The best way to learn more about Kotlin’s delegate is to turn it into Java code and examine it.

#daqiKotlin.kt
class Person{
    var name:String by Delegates.observable("daqi"){ property, oldValue, newValue ->
        println("${property.name}  oldValue = $oldValue  newValue = $newValue")}}Copy the code

Decompiled Java code:

public final class Person$$special$$inlined$observableThe $1 extends ObservableProperty {
   // $FF: synthetic field
   final Object $initialValue;

   public Person$$special$$inlined$observableThe $1(Object $captured_local_variableThe $1, Object $super_call_param$2) {
      super($super_call_param$2);
      this.$initialValue = $captured_local_variableThe $1;
   }

   protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      String newValue = (String)newValue;
      String oldValue = (String)oldValue;
      int var7 = false;
      String var8 = property.getName() + " oldValue = " + oldValue + " newValue = " + newValue;
      System.out.println(var8);
   }
}

public final class Person {
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name"."getName()Ljava/lang/String;"))};
   @NotNull
   private final ReadWriteProperty name$delegate;

   @NotNull
   public final String getName() {
      return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "
      
       "
      ?>);
      this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
   }

   public Person() {
      Delegates var1 = Delegates.INSTANCE;
      Object initialValue$iv = "daqi";
      ReadWriteProperty var5 = (ReadWriteProperty)(new Person$$special$$inlined$observableThe $1(initialValue$iv, initialValue$iv));
      this.name$delegate= var5; }}Copy the code
  • 1, create an inheritance fromObservablePropertythePerson? special? inlined$observable$1Class, becauseDelegates.observable()Is to return an anonymousObservablePropertyObject.
  • 2,PersonClass defines onename$delegateProperty that points tonameProperty, that isPerson? special? inlined$observable$1Class.
  • 3,PersonIn the classnameProperty will be converted togetName()andsetName().
  • 4,nameProperties of thegetandsetAn internal call to the methodname$delegateThe correspondingsetValue()andgetValue().
  • The KProperty array holds information about the Name attribute of the Personr class that was reflected by Kotlin. In the callname$delegatethesetValue()andgetValue()Pass this information as a parameter.

Background fields and background properties

If you look at the decomcompiled Java source code, you might wonder why Kotlin’s Person name property is not defined in Java’s Person, but only the get and set methods for that property.

This involves the question of Kotlin’s behind-the-scenes fields. What is Kotlin’s behind-the-scenes field? It is very clear in Chinese:

The corresponding Java variable is available only when the property with the underlying field is converted to Java code.

The Kotlin attribute must have one of the following conditions:

  • Use the defaultgetter / setterProperty, there must be a background field. forvarProperties, as long asgetter / setterOne of them uses the default implementation, which generates the background fields;
  • In the customgetter / setterThe use of thefieldThe properties of the

So you can understand why extended properties can’t use field, because extended properties don’t really add new properties to the class and can’t have hidden fields. In addition, the get and set methods of the delegate attribute internally call the getValue() or setValue() of the proxy object without using field, and neither of them use the default get and set methods.

conclusion

  • Class delegates make it easy to implement decorator design patterns, and developers only care about the methods that need to be extended.
  • The delegate property is the property that takes that propertysetandgetAssigned to a proxy objectsetValuegetValueTo deal with.
  • Delegate properties are also a convention.setValuegetValueMust be withoperatorKeyword modification.
  • The Kotlin standard library providesReadOnlyPropertyReadWritePropertyInterfaces that developers can implement to providecorrectthegetValue()Methods andsetValue()Methods.
  • valAttribute can be lazily initialized with the delegate attribute, usinglazy()Set up the initialization process and automatically return the proxy object.
  • Delegates.observable()The ability to receive notifications when delegated properties change, somewhat like ACC’s LiveData
  • Delegates.vetoable()The ability to receive notification before a delegated property changes and to decide whether to assign a new value to that property.
  • Delegates.notNull()Can be used as any typevarVariable is lazily initialized
  • The corresponding Java variable is available only when the property with the underlying field is converted to Java code.

References:

  • Kotlin in Action
  • Kotlin website

Android Kotlin series:

Kotlin’s Knowledge generalization (I) — Basic Grammar

Kotlin knowledge generalization (2) – make functions easier to call

Kotlin’s knowledge generalization (iii) — Top-level members and extensions

Kotlin knowledge generalization (4) – interfaces and classes

Kotlin’s knowledge induction (v) — Lambda

Kotlin’s knowledge generalization (vi) — Type system

Kotlin’s knowledge induction (7) — set

Kotlin’s knowledge induction (viii) — sequence

Kotlin knowledge induction (ix) — Convention

Kotlin’s knowledge induction (10) — Delegation

Kotlin’s knowledge generalization (xi) — Higher order functions

Kotlin’s generalization of knowledge (xii) – generics

Kotlin’s Generalization of Knowledge (XIII) — Notes

Kotlin’s Knowledge induction (xiv) — Reflection