Delegated properties
With some common kinds of properties, even though you can implement them manually every time you need them, it is more helpful to implement them once, add them to a library, and reuse them later. For example:
-
Lazy properties: the value is computed only on first access.
-
Observable properties: listeners are 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:
The syntax is: val/var <property name>: <Type> by <expression>
. The expression after by
is a delegate, because the get()
(and set()
) that correspond to the property will be delegated to its getValue()
and setValue()
methods. Property delegates don't have to implement an interface, but they have to provide a getValue()
function (and setValue()
for var
s).
For example:
When you read from p
, which delegates to an instance of Delegate
, the getValue()
function from Delegate
is called. Its first parameter is the object you read p
from, and the second parameter holds a description of p
itself (for example, you can take its name).
This prints:
Similarly, when you assign to p
, the setValue()
function is called. The first two parameters are the same, and the third holds the value being assigned:
This prints:
The specification of the requirements for the delegated object can be found below.
You can declare a delegated property inside a function or code block; it doesn't have to 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 properties
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.
By default, the evaluation of lazy properties is synchronized: the value is computed only in one thread, but all threads will see the same value. If the synchronization of the initialization delegate is not required to allow multiple threads to execute it simultaneously, pass LazyThreadSafetyMode.PUBLICATION
as a parameter to lazy()
.
If you're sure that the initialization will always happen in 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 related overhead.
Observable properties
Delegates.observable()
takes two arguments: the initial value and a handler for modifications.
The handler is called every time you assign to the property (after the assignment has been performed). It has three parameters: the property being assigned to, the old value, and the new value:
If you want to intercept assignments and veto them, use vetoable()
instead of observable()
. The handler passed to vetoable
will be called before the assignment of a new property value.
Delegating to another property
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 ::
qualifier in the delegate name, for example, this::delegate
or MyClass::delegate
.
This may be useful, for example, when you want to rename a property in a backward-compatible way: introduce a new property, annotate the old one with the @Deprecated
annotation, and delegate its implementation.
Storing properties in a map
One common use case is storing the values of properties in a map. This comes up often in applications for things like parsing JSON or performing other dynamic tasks. In this case, you can use the map instance itself as the delegate for a delegated property.
In this example, the constructor takes a map:
Delegated properties take values from this map through string keys, which are associated with the names of properties:
This also works for var
's properties if you use a MutableMap
instead of a read-only Map
:
Local delegated properties
You can declare local variables as delegated properties. For example, you can make a local variable lazy:
The memoizedFoo
variable will be computed on first access only. If someCondition
fails, the variable won't be computed at all.
Property delegate requirements
For a read-only property (val
), a delegate should provide an operator function getValue()
with the following parameters:
thisRef
must be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).property
must be of typeKProperty<*>
or its supertype.
getValue()
must return the same type as the property (or its subtype).
For a mutable property (var
), a delegate has to additionally provide an operator function setValue()
with the following parameters:
thisRef
must be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).property
must be of typeKProperty<*>
or its supertype.value
must be of the same type as the property (or its supertype).
getValue()
and/or setValue()
functions can be provided either as member functions of the delegate class or as extension functions. The latter is handy when you need to delegate a property to an object that 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, by 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()
. This means you can pass a ReadWriteProperty
whenever a ReadOnlyProperty
is expected.
Translation rules for delegated properties
Under the hood, the Kotlin compiler generates auxiliary properties for some kinds of delegated properties and then delegates to them.
For example, for the property prop
it generates the hidden property prop$delegate
, and the code of the accessors simply delegates to this additional property:
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.
Optimized cases for delegated properties
The $delegate
field will be omitted if a delegate is:
A referenced property:
class C<Type> { private var impl: Type = ... var prop: Type by ::impl }A named object:
object NamedObject { operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ... } val s: String by NamedObjectA final
val
property with a backing field and a default getter in the same module:val impl: ReadOnlyProperty<Any?, String> = ... class A { val s: String by impl }A constant expression, enum entry,
this
,null
. The example ofthis
:class A { operator fun getValue(thisRef: Any?, property: KProperty<*>) ... val s by this }
Translation rules when delegating to another property
When delegating to another property, the Kotlin compiler generates immediate access to the referenced property. This means that the compiler doesn't generate the field prop$delegate
. This optimization helps save memory.
Take the following code, for example:
Property accessors of the prop
variable invoke the impl
variable directly, skipping the delegated property's getValue
and setValue
operators, and thus the KProperty
reference object is not needed.
For the code above, the compiler generates the following code:
Providing a delegate
By defining the provideDelegate
operator, you can extend the logic for 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, to check the property name before binding, you can write something like this:
The parameters of provideDelegate
are the same as those of getValue
:
thisRef
must be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended);property
must be of typeKProperty<*>
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:
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):
Note that the provideDelegate
method affects only the creation of the auxiliary property and doesn't affect the code generated for the getter or the setter.
With the PropertyDelegateProvider
interface from the standard library, you can create delegate providers without creating new classes.