English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Kotlin Delegates

The delegation pattern is a basic technique in software design patterns. In the delegation pattern, two objects participate in processing the same request, and the object receiving the request delegates the request to another object to handle.

Kotlin directly supports the delegation pattern, which is more elegant and concise. Kotlin implements delegation through the keyword by.

Class delegation

Class delegation is the actual implementation of a method defined in a class by calling the method of another class object.

In the following example, the derived class Derived inherits all methods of the interface Base and delegates the execution of these methods to an object of the Base class passed in.

// Create an interface
interface Base {   
    fun print()
{}
// Implementation class of this interface
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
{}
// Established by keyword by delegate class
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // Output 10
{}

In the Derived declaration, the by clause indicates that b is stored internally in the Derived object instance, and the compiler will generate all methods inherited from the Base interface and forward the calls to b.

Property delegation

Property delegation refers to a class where the value of a certain property is not defined directly in the class, but is entrusted to an agent class, thus achieving unified management of the class properties.

Property delegation syntax format:

val/var <属性名>: <类型> by <expression>
  • var/val: property type (mutable/Read-only)

  • Property name: the name of the property

  • Type: the data type of the property

  • Expression: delegation proxy class

The expression after the by keyword is the delegation, the get() method (and set() method) of the property will be delegated to the getValue() and setValue() methods of this object. The property delegation does not need to implement any interface, but it must provide the getValue() function (for var properties, the setValue() function is also required).

Define a delegated class

This class needs to include the getValue() method and the setValue() method, and the parameter thisRef is the object of the class being delegated, and prop is the object of the property being delegated.

import kotlin.reflect.KProperty
// Define a class containing property delegation
class Example {
    var p: String by Delegate()
{}
// The class of delegation
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, here delegated ${property.name} attribute"
    {}
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef's ${property.name} attribute is assigned the value $value")
    {}
{}
fun main(args: Array<String>) {
    val e = Example()
    println(e.p)     // Access this property, call the getValue() function
    e.p = "w3codebox"   // Call the setValue() function
    println(e.p)
{}

The output result is:

Example@433c675d, here delegates the p property
Example@433c675d's p attribute is assigned the value w3codebox
Example@433c675d, here delegates the p property

Standard Delegation

Kotlin's standard library has built-in many factory methods to implement property delegation.

Lazy Property

lazy() is a function that takes a Lambda expression as a parameter and returns a function that returns a Lazy<T> example. The returned example can be used as a delegate to implement lazy properties: The first call to get() executes the lambda expression passed to lazy() and records the result, and subsequent calls to get() simply return the recorded result.

val lazyValue: String by lazy {
    println("computed!")     // First call output, second call does not execute
    "Hello"
{}
fun main(args: Array<String>) {
    println(lazyValue)   // First execution, execute the expression twice
    println(lazyValue)   // Second execution, only output the return value
{}

Execution output result:

computed!
Hello
Hello

Observable Property

Observable can be used to implement the observer pattern.

The Delegates.observable() function accepts two parameters: the first is the initial value, and the second is the property value change event handler.

After the property assignment, an event handler is executed, which has three parameters: the property assigned, the old value, and the new value:

import kotlin.properties.Delegates
class User {
    var name: String by Delegates.observable("Initial value") {
        prop, old, new ->
        println("Old value: $old -> New value: $new"
    {}
{}
fun main(args: Array<String>) {
    val user = User()
    user.name = "First assignment"
    user.name = "Second assignment"
{}

Execution output result:

Old value: Initial value -> New value: First assignment
Old value: First assignment -> New value: Second assignment

Store properties in a map

A common use case is to store property values in a map. This often appears in applications like parsing JSON or doing other 'dynamic' things. In this case, you can use the mapping example itself as a delegate to implement delegate properties.

class Site(val map: Map<String, Any?>) {
    val name: String by map
    val url: String by map
{}
fun main(args: Array<String>) {
    // The constructor accepts a mapping parameter
    val site = Site(mapOf(
        "name" to "Basic Tutorial Website",
        "url" to "www.w"3codebox.com"
    ))
    
    // Reading mapping values
    println(site.name)
    println(site.url)
{}

Execution output result:

Basic Tutorial Website
www.oldtoolbag.com

If you use the var property, you need to change Map to MutableMap:

class Site(val map: MutableMap<String, Any?>) {
    val name: String by map
    val url: String by map
{}
fun main(args: Array<String>) {
    var map: MutableMap<String, Any?> = mutableMapOf(
            "name" to "Basic Tutorial Website",
            "url" to "www.oldtoolbag.com"
    )
    val site = Site(map)
    println(site.name)
    println(site.url)
    println("--------------)
    map.put("name", "Google")
    map.put("url", "www.google.com")
    println(site.name)
    println(site.url)
{}

Execution output result:

Basic Tutorial Website
www.oldtoolbag.com
--------------
Google
www.google.com

Not Null

NotNull is suitable for cases where the property value cannot be determined at the initialization stage.

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
{}
foo.notNullBar = "bar"
println(foo.notNullBar)

Note that an exception will be thrown if the property is accessed before it is assigned.

Local delegate properties

You can declare local variables as delegate properties. For example, you can make a local variable lazily initialized:

fun example(computeFoo: () -> -> Foo) {
    val memoizedFoo by lazy(computeFoo)
    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    {}
{}

The memoizedFoo variable will only be calculated when accessed for the first time. If someCondition fails, the variable will not be calculated at all.

Property delegate requirements

For read-only properties (that is, the val property), its delegate must provide a function named getValue(). This function accepts the following parameters:

  • thisRef - must be the same type as the property owner type (for extension properties - the type being extended) or its super type

  • property - must be the type KProperty or its super type

This function must return the same type (or its sub type) as the property.

For a mutable (var) property (that is, a var property), in addition to the getValue() function, its delegate must also provide a function named setValue(), which accepts the following parameters:

  • thisRef - must be the same type as the property owner type (for extension properties - the type being extended) or its super type

  • property - must be the type KProperty or its super type

  • new value - must be the same type as the property or its super type.

Compilation rules

Behind each delegate property implementation, the Kotlin compiler generates auxiliary properties and delegates to them. For example, for the property prop, a hidden property prop$delegate is generated, and the code for the accessor simply delegates to this 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)
{}

The Kotlin compiler provides all the necessary information about prop in the parameters: the first parameter this refers to an instance of the external class C, and this::prop is a reflection object of the KProperty type, which describes prop itself.

Provide delegate

By defining the provideDelegate operator, you can extend the logic of the delegate object for creating property implementations. If the object on the right side of by defines provideDelegate as a member or extension function, then this function will be called to create an example of a property delegate.

A possible use case for provideDelegate is to check property consistency when creating properties (not just in their getter or setter).

For example, if you want to check the property name before binding, you can write it like this:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // Create delegate
    {}
    private fun checkProperty(thisRef: MyUI, name: String) { …… }
{}
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }
class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
{}

The parameters of provideDelegate are the same as those of getValue:

  • thisRef —— Must be the same type as the property owner type (for extension properties—refers to the type being extended) or its superclass

  • property —— Must be of type KProperty or its superclass.

During the creation of the MyUI instance, call the provideDelegate method for each property and immediately perform the necessary validation.

If you don't have the ability to intercept the binding between the intercepted property and its delegate, to achieve the same functionality, you must explicitly pass the property name, which is not very convenient:

// Check property name without using the 'provideDelegate' feature
class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
{}
fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // Create delegate
{}

In the generated code, the provideDelegate method is called to initialize the auxiliary prop$delegate property. Compare the code generated for the property declaration val prop: Type by MyDelegate() with the code above (when the provideDelegate method does not exist):

class C {
    var prop: Type by MyDelegate()
{}
// This code is for when the 'provideDelegate' feature is available
// Code generated by the compiler:
class C {
    // Call 'provideDelegate' to create additional 'delegate' properties
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
{}

Please note that the provideDelegate method only affects the creation of auxiliary properties and does not affect the code generated for getters or setters.