W3cubDocs

/Kotlin

Delegated Properties

There are certain common kinds of properties, that, though we can implement them manually every time we need them, would be very nice to implement once and for all, and put into a library. Examples include:

  • lazy properties: the value gets computed only upon first access;
  • observable properties: listeners get notified about changes to this property;
  • storing properties in a map, instead of a separate field for each property.

To cover these (and other) cases, Kotlin supports delegated properties:

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

The syntax is: val/var <property name>: <Type> by <expression>. The expression after by is the delegate, because get() (and set()) corresponding to the property will be delegated to its getValue() and setValue() methods. Property delegates don’t have to implement any interface, but they have to provide a getValue() function (and setValue() — for vars). For example:

import kotlin.reflect.KProperty

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.")
    }
}

When we read from p that delegates to an instance of Delegate, the getValue() function from Delegate is called, so that its first parameter is the object we read p from and the second parameter holds a description of p itself (e.g. you can take its name). For example:

val e = Example()
println(e.p)

This prints:

Example@33a17727, thank you for delegating ‘p’ to me!

Similarly, when we assign to p, the setValue() function is called. The first two parameters are the same, and the third holds the value being assigned:

e.p = "NEW"

This prints

NEW has been assigned to ‘p’ in Example@33a17727.

The specification of the requirements to the delegated object can be found below.

Note that since Kotlin 1.1 you can declare a delegated property inside a function or code block, it shouldn't necessarily be a member of a class. Below you can find an example.

Standard delegates

The Kotlin standard library provides factory methods for several useful kinds of delegates.

Lazy

lazy() is a function that takes a lambda and returns an instance of Lazy<T> which can serve as a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result, subsequent calls to get() simply return the remembered result.

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

fun main() {
    println(lazyValue)
    println(lazyValue)
}

By default, the evaluation of lazy properties is synchronized: the value is computed only in one thread, and all threads will see the same value. If the synchronization of initialization delegate is not required, so that multiple threads can execute it simultaneously, pass LazyThreadSafetyMode.PUBLICATION as a parameter to the lazy() function. And if you're sure that the initialization will always happen on the same thread as the one where you use the property, you can use LazyThreadSafetyMode.NONE: it doesn't incur any thread-safety guarantees and the related overhead.

Observable

Delegates.observable() takes two arguments: the initial value and a handler for modifications. The handler is called every time we assign to the property (after the assignment has been performed). It has three parameters: a property being assigned to, the old value and the new one:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

If you want to intercept assignments and "veto" them, use vetoable() instead of observable(). The handler passed to the vetoable is called before the assignment of a new property value has been performed.

Delegating to another property

Since Kotlin 1.4, a property can delegate its getter and setter to another property. Such delegation is available for both top-level and class properties (member and extension). The delegate property can be:

  • a top-level property
  • a member or an extension property of the same class
  • a member or an extension property of another class

To delegate a property to another property, use the proper :: qualifier in the delegate name, for example, this::delegate or MyClass::delegate.

var topLevelInt: Int = 0

class ClassWithDelegate(val anotherClassInt: Int)
//sampleStart
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
//sampleEnd

This may be useful, for example, when you want to rename a property in a backward-compatible way: you introduce a new property, annotate the old one with the @Deprecated annotation, and delegate its implementation.

class MyClass {
   var newName: Int = 0
   @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
   var oldName: Int by this::newName
}

fun main() {
   val myClass = MyClass()
   // Notification: 'oldName: Int' is deprecated.
   // Use 'newName' instead
   myClass.oldName = 42
   println(myClass.newName) // 42
}

Storing properties in a map

One common use case is storing the values of properties in a map. This comes up often in applications like parsing JSON or doing other “dynamic” things. In this case, you can use the map instance itself as the delegate for a delegated property.

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

In this example, the constructor takes a map:

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

Delegated properties take values from this map (by the string keys –– names of properties):

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

fun main() {
    val user = User(mapOf(
        "name" to "John Doe",
        "age"  to 25
    ))
//sampleStart
    println(user.name) // Prints "John Doe"
    println(user.age)  // Prints 25
//sampleEnd
}

This works also for var’s properties if you use a MutableMap instead of read-only Map:

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int     by map
}

Local delegated properties

You can declare local variables as delegated properties. For instance, you can make a local variable lazy:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

The memoizedFoo variable will be computed on the first access only. If someCondition fails, the variable won't be computed at all.

Property delegate requirements

Here we summarize requirements to delegate objects.

For a read-only property (val), a delegate has to provide an operator function getValue() with the following parameters:

  • thisRef — must be the same or a supertype of the property owner (for extension properties — the type being extended).
  • property — must be of type KProperty<*> or its supertype.

getValue() must return the same type as the property (or its subtype).

class Resource

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

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

For a mutable property (var), a delegate has to additionally provide an operator function setValue() with the following parameters:

  • thisRef — must be the same or a supertype of the property owner (for extension properties — the type being extended).
  • property — must be of type KProperty<*> or its supertype.
  • value — must be of the same type as the property (or its supertype).
class Resource

class Owner {
    var varResource: Resource by ResourceDelegate()
}

class ResourceDelegate(private var resource: Resource = Resource()) {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return resource
    }
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
        if (value is Resource) {
            resource = value
        }
    }
}

getValue() and/or setValue() functions may be provided either as member functions of the delegate class or extension functions. The latter is handy when you need to delegate property to an object which doesn't originally provide these functions. Both of the functions need to be marked with the operator keyword.

You can create delegates as anonymous objects without creating new classes using the interfaces ReadOnlyProperty and ReadWriteProperty from the Kotlin standard library. They provide the required methods: getValue() is declared in ReadOnlyProperty; ReadWriteProperty extends it and adds setValue(). Thus, you can pass a ReadWriteProperty whenever a ReadOnlyProperty is expected.

fun resourceDelegate(): 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()

Translation rules

Under the hood, for every delegated property the Kotlin compiler generates an auxiliary property and delegates to it. For instance, for the property prop the hidden property prop$delegate is generated, and the code of the accessors 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)
}

The Kotlin compiler provides all the necessary information about prop in the arguments: the first argument this refers to an instance of the outer class C and this::prop is a reflection object of the KProperty type describing prop itself.

Note that the syntax this::prop to refer a bound callable reference in the code directly has only been available since Kotlin 1.1.

Providing a delegate

By defining the provideDelegate operator you can extend the logic of creating the object to which the property implementation is delegated. If the object used on the right-hand side of by defines provideDelegate as a member or extension function, that function will be called to create the property delegate instance.

One of the possible use cases of provideDelegate is to check the consistency of the property upon its initialization.

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

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
    
class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

The parameters of provideDelegate are the same as for getValue:

  • thisRef — must be the same or a supertype of the property owner (for extension properties — the type being extended);
  • property — must be of type KProperty<*> or its supertype.

The provideDelegate method is called for each property during the creation of the MyUI instance, and it performs the necessary validation right away.

Without this ability to intercept the binding between the property and its delegate, to achieve the same functionality you'd have to pass the property name explicitly, which isn't very convenient:

// Checking the property name without "provideDelegate" functionality
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 generated code for the property declaration val prop: Type by MyDelegate() with the generated code above (when the provideDelegate method is not present):

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

// this code is generated by the compiler 
// when the 'provideDelegate' function is available:
class C {
    // calling "provideDelegate" to create the additional "delegate" property
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

Note that the provideDelegate method affects only the creation of the auxiliary property and doesn't affect the code generated for getter or setter.

With the PropertyDelegateProvider interface from the standard library, you can create delegate providers without creating new classes.

val provider = PropertyDelegateProvider { thisRef: Any?, property ->
    ReadOnlyProperty<Any?, Int> {_, property -> 42 }
}

val delegate: Int by provider

© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/delegated-properties.html