There are some common attributes in our development, although we can implement them manually every time we need them. But it would be better to implement them once and add them to the official library. Examples include:

  • The inertia properties: Computes values only on the first access
  • Observableattribute: The observer is notified of this property change

To cover these cases, Kotlin supports delegate attributes.

class Example {
    var p: String by Delegate()
}
Copy the code

grammar

val/var <property name>: <Type> by <expression>
Copy the code

The following expression by is a delegate, because the get() (and set()) corresponding to the property are delegated to its getValue() and setValue() methods. Property delegates do not have to implement any interface, but they must provide a getValue() function (and setValue()– for vars).

For example,

class Delegate {
    operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")}}Copy the code

Delegate, when you read an instance of the Delegate from p, calls getValue() to the from function Delegate, so its first argument is the object p you read from, and its second argument contains a description of P itself (for example, you can use its name).

val e = Example()
println(e.p)
Copy the code

Print result:

Example@33a17727, thank you forDelegating 'p' to me!Copy the code

Similarly, setValue() calls this function when you assign to p. The first two arguments are the same, and the third holds the assigned value:

e.p = "NEW"
Copy the code

Print result:

NEW has been assigned to 'P'in Example@33a17727.
Copy the code

Agents in the standard library

Lazy properties

Lazy () is a function that takes a lambda and returns an instance of lazy

that can be used as a delegate to implement lazy properties: the first call to get() executes the passed lambdalazy() and remembers the result, and subsequent calls to get() return only the remembered result.

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(a) {
    println(lazyValue)
    println(lazyValue)
}
Copy the code

By default, the evaluation of lazy attributes is synchronous: the value is evaluated in only one thread, and all threads see the same value. If you don’t need to be synchronized initialization, so that multiple threads can perform at the same time, the LazyThreadSafetyMode. PUBLICATION passed as a parameter to lazy () function.

If you determine initialization will always happen in with your use of the properties of the thread on the thread of the same, you can use LazyThreadSafetyMode. NONE: it won’t create any thread safety guarantee and related expenses.

Difference between lateinit and by lazy

As far as I’m concerned, we don’t use lateInit in our projects as much as possible, because we know how to initialize a lot of things. Lateinit The only application I can think of is when a variable is passed externally to initialize a property and it must not be null. Since Kotlin, like Swift, has strict checks, we can use it to tell the compiler that we can’t initialize it until later. This is also a point that cannot be solved elegantly in Swift.

Recently in the company’s internal projects, this keyword was abused, looking at the thief uncomfortable. Alas…

Observable attribute

Delegates.Observable () accepts two parameters: the initial value and the modified closure. The closure is called each time an assignment is made to a property (after the assignment is performed). It takes three parameters: the assigned attribute, the old value, and the new value:

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")}}fun main(a) {
    val user = User()
    user.name = "first"
    user.name = "second"
}
Copy the code

If you want to intercept assignments and reject them, use vetoable() instead of Observable ().

Delegate to another property

A property can delegate its setter() and getter() to another property. This proxy can be used for top-level and class attributes (members or extensions). This proxy attribute can be:

  • atop-levelattribute
  • A member attribute or extension attribute in the same class
  • Member attributes or extended attributes in different classes

For cases like this where one attribute is delegated to another, use the syntax ::proper

var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int.val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt

    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
Copy the code

This is actually quite useful. For example, if we want to rename properties in a backward-compatible way: introduce a new property, annotate the old property with @deprecated annotations, and then delegate its implementation.

class MyClass {
   var newName: Int = 0
   @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
   var oldName: Int by this::newName
}
fun main(a) {
   val myClass = MyClass()
   // Notification: 'oldName: Int' is deprecated.
   // Use 'newName' instead
   myClass.oldName = 42
   println(myClass.newName) / / 42
}
Copy the code

The above two examples are provided in the official documentation (1.5.10) and are not supported by older versions of Kotlin

The property broker

For the read-only attribute val, the delegate should provide the operator function getValue() with the following parameters:

ThisRef must be the same or supertype of the property owner (for extended properties, it should be the type being extended).

Property must be of type KProperty<*> or its supertype.

GetValue () must return the same type as the attribute (or its subtype).

class Owner {
    val valResource: Resource by ResourceDelegate()
}

class ResourceDelegate {
    operator fun getValue(thisRef: Owner, property: KProperty< * >): Resource {
        return Resource()
    }
}
Copy the code

For the variable var attribute, the delegate must provide an additional setValue() operator function that takes the following arguments:

ThisRef must be the same or supertype of the property owner (for extended properties, it should be the type being extended).

Property must be of type KProperty<*> or its supertype.

A value must have the same type as an attribute (or its supertype).

The getValue()/setValue() functions can be provided as member functions or extension functions of the delegate class. The latter is handy when you need to delegate properties to objects that do not initially provide these capabilities. Both functions need to be marked with the operator keyword.

You can create delegates as anonymous objects without having to create new classes using the interfaces ReadOnlyProperty and ReadWritePropertyKotlin libraries. They provide the required methods: getValue() is declared in ReadOnlyProperty, and ReadWriteProperty extends it and adds setValue(). Therefore, you can pass ReadWriteProperty when you need to pass ReadOnlyProperty.

fun resourceDelegate(a): ReadWriteProperty<Any? .Int> =
    object: ReadWriteProperty<Any? .Int> {
        var curValue = 0
        override fun getValue(thisRef: Any? , property:KProperty< * >): Int = curValue
        override fun setValue(thisRef: Any? , property:KProperty<*>, value: Int) {
            curValue = value
        }
    }

val readOnly: Int by resourceDelegate()  // ReadWriteProperty as val
var readWrite: Int by resourceDelegate()
Copy the code

Transformation rules

The Kotlin compiler generates a secondary property for each delegate property and delegates to it. For example, to generate a prop hidden property prop$delegate for a property, the accessor’s code simply delegates to this additional property:

class C {
    var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this.this::prop)
        set(value: Type) = prop$delegate.setValue(this.this::prop, value)
}
Copy the code

The Kotlin compiler prop provides all the necessary information in its arguments: the first argument, this, refers to an instance of an external class, C, and this:: Prop is a reflection object that KProperty describes the type of the prop itself.

Class representative

interface Base { val message: String fun print() } class BaseImpl(val x: Int) : Base { override val message = "BaseImpl: x = $x" override fun print() { println(message) } } class Derived(b: Base) : Base by b { // This property is not accessed from b's implementation of `print` override val message = "Message of Derived" } fun main() { val b = BaseImpl(10) val derived = Derived(b) derived.print() println(derived.message) } -- -- -- -- -- -- -- -- -- -- - the execution result -- -- -- -- -- -- -- -- -- -- - BaseImpl: x = 10 Message of DerivedCopy the code

conclusion

Class proxying is the act of proxying a method to an instance of another class.

Property proxies simply delegate getter() and setter() for properties to other properties.