This article is only for recording learning, so if there are some mistakes or wrong understanding, please correct.

Kotlin’s Study Notes (6)

Description:

  1. The use of lateinit
  2. Delegates and delegate properties
  3. Standard delegation and delayed delegation

1. lateinit

In Java we can usually define an object without assigning a value

 private int index;
Copy the code

But in Kotlin if we do the same it will prompt us to do an initialization, of course we can also use latelnit to set the object to be initialized later. To do it)

var index: int   / / an error
var index :int = 1   // Direct initialization is complete
var latelnit index :int   // Initialize later
Copy the code

2. Kotlin entrust

The delegation pattern is one of the basic techniques in software design patterns. In delegate mode, there are two objects involved in processing the same request, and the receiving object delegates the request to another object to handle it.

Kotlin directly supports the delegate pattern, which is more elegant and concise. Kotlin implements delegates with the keyword BY.

The delegate pattern has proven to be a good alternative to implementing inheritance, and Kotlin can support it natively with zero boilerplate code. A Derived class can implement an interface Base by delegating all its public members to a specified object

interface Base {
    fun print(a)
}

class BaseImpl(val x: Int) : Base {
    override fun print(a) { print(x) }
}

class Derived(b: Base) : Base by b

// If there is no delegate, we need to override the interface

class Derived(b: Base) : Base {
    override fun print(a) {
        TODO("not implemented")}}fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print()
}
Copy the code

2.1 Overrides interface members implemented by delegates

Override is as expected: the compiler uses the override implementation rather than the one in the delegate object. If override fun print() {print(” ABC “)} were added to the Derived, then the program would print “ABC” instead of “10” when print was called:

interface Base {
    fun printMessage(a)
    fun printMessageLine(a)
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage(a) { print(x) }
    override fun printMessageLine(a) { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage(a) { print("abc")}}fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).printMessage()     // Print ABC
    Derived(b).printMessageLine()   // If not overridden, 10 will be printed
}
Copy the code

3. Delegate properties

There are some common property types, and while we can implement them manually every time we need them, it would be nice to implement them once and put them in a library for everyone. Examples include:

  1. Lazy properties: the value is calculated only on the first access;
  2. Observable Properties: Listeners are notified of changes to this property;
  3. Store multiple attributes in a map, rather than each in a separate field.

To cover these (and other) cases, Kotlin supports the delegate property:

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

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.")}}fun main(args: Array<String>) {
    val e = Example()
    println(e.p)

    e.p = "ymc"
}
Copy the code

The output

Example@300ffa5d, thank you for delegating 'p' to me!
ymc has been assigned to 'p' in Example@300ffa5d.
Copy the code

3.1 Standard entrustment

Syntax: val/var < attribute name >: < type > by < expression >

Property delegates do not have to implement any interface, but do need to provide a getValue() function (and setValue() — in the case of var properties), Because the property’s get() (and set()) are delegated to its getValue() and setValue() methods. You can also directly inherit the ReadWriteProperty and implement the methods in it, so you can avoid the error of writing it by yourself. For example, Kotlin source code implements the null delegate property like this:

public object Delegates {
  public fun <T: Any> notNull(a): ReadWriteProperty<Any? , T> = NotNullVar() }private 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

In this case, NotNullVar inherits ReadWriteProperty and implements its two methods: Delegates.notnull () belongs to the delegate property.

3.2 Entrustment Requirements

For a read-only property (that is, one declared by val), the delegate must provide a function called getValue that takes the following arguments (you can implement this method by inheriting ReadOnlyProperty) :

  1. ThisRef — must be the same as the property owner type (for extended properties — the type being extended) or its supertype,

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

  3. For a mutable property (that is, one declared by var), the delegate must provide an additional function called setValue that takes the following arguments (you can implement this method by inheriting from ReadWriteProperty) :

    1. ThisRef — same as getValue(),

    2. Property — same as getValue(),

    3. New value – must be of the same type as the property or its supertype.

The getValue() and/or setValue() functions can be provided by member functions of the delegate class or by extension functions. The latter is more convenient when you need to delegate properties to objects that are not provided with these functions. Both functions need to be marked with the operator keyword.

3.3 Translation Rules

Behind the implementation of each delegate property, the Kotlin compiler generates the auxiliary property and delegates to it. For example, for the property prop, the hidden property prop$delegate is generated, and the accessor’s code simply delegates to the additional property:

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

// This is the corresponding code generated by the compiler:
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 provides all the necessary information about the prop in its arguments: the first argument, this, refers to an instance of the external class C and this::prop is a reflection object of type KProperty that describes the prop itself.

4. Standard delegation

4.1 Delayed Delegate

Lazy () is a function that takes a lambda and returns an instance of lazy, which can be used as a delegate to implement the delay property: The first call to get() executes the lambda expression passed to lazy() and records the result; subsequent calls to get() simply return the recorded result.

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

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}
/ / output:
computed!  // Only the first initialization
Hello
Hello
Copy the code

By default, evaluation of the lazy property is synchronized: the value is evaluated in only one thread, and all threads see the same value. Initialization if entrusted by the synchronous lock is not required, so that multiple threads can perform at the same time, it will be LazyThreadSafetyMode. PUBLICATION passed as a parameter to lazy () function. Initialization and if you are sure to happen in a single thread, so you can use LazyThreadSafetyMode. NONE mode, it will not have any thread safety guarantee and related expenses.

4.2 Observable Property Observable

Delegates.Observable () accepts two parameters: the initial value and the modifie-time handler. This handler is called whenever we assign a value to a property (executing after the assignment). It takes three arguments: the assigned property, the old value, and the new value:

import kotlin.properties.Delegates

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

The results

<no name> -> first
first -> second
Copy the code

4.3 Saving Attributes in a Mapping

A common use case is to store property values in a map. This is often seen in applications like parsing JSON or doing other “dynamic” things. In this case, you can implement the delegate property by using the mapping instance itself as the delegate.

// Since Map is read-only, we can replace var with MutableMap if we need it

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

fun main(args: Array<String>) {
    val user = User(mapOf(
        "name" to "John Doe"."age"  to 25
    ))
    println(user.name) // Prints "John Doe"
    println(user.age)  // Prints 25
}
Copy the code

Kotlin’s Study Notes (8)