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 from
ObservableProperty
thePerson? special? inlined$observable$1
Class, becauseDelegates.observable()
Is to return an anonymousObservableProperty
Object. - 2,
Person
Class defines onename$delegate
Property that points toname
Property, that isPerson? special? inlined$observable$1
Class. - 3,
Person
In the classname
Property will be converted togetName()
andsetName()
. - 4,
name
Properties of theget
andset
An internal call to the methodname$delegate
The correspondingsetValue()
andgetValue()
. - The KProperty array holds information about the Name attribute of the Personr class that was reflected by Kotlin. In the call
name$delegate
thesetValue()
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 default
getter
/setter
Property, there must be a background field. forvar
Properties, as long asgetter
/setter
One of them uses the default implementation, which generates the background fields; - In the custom
getter
/setter
The use of thefield
The 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 property
set
andget
Assigned to a proxy objectsetValue
和getValue
To deal with. - Delegate properties are also a convention.
setValue
和getValue
Must be withoperator
Keyword modification. - The Kotlin standard library provides
ReadOnlyProperty
或ReadWriteProperty
Interfaces that developers can implement to providecorrectthegetValue()
Methods andsetValue()
Methods. val
Attribute 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 LiveDataDelegates.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 typevar
Variable 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