Property Delegation Kotlin
Introduction
Welcome to another learning session, today we are covering property delegation in kotlin. To break down to topic lets understand the terminologies used in out title.
Properties are ussually backed directly by corresponding fields. Normally, properties have associated fields directly backing them, however this isn’t always the case; properties can still exist as long as they are appropriately exposed to the outside world.
What’s a delegate class ?
This is a class that provides the property of a value and handles its changes. It will help us to assign or pass on(delegate), the getter-setter logic altogether to a different class so that it can help us in reusing the code.
This is an alternative for inheritance property.
Examples of built in delegate property
- Lazy properties- 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.
Lazy properties
Lazy() takes a lambda and returns an instance of Lazy<T>, which can serve as a delegate for implementing lazy property.
The first call to get() executes the lambda passed to get lazy() and remembers the result. Subsequent calls to get() simply returns the remembered results.
The evolution of lazy properties is synchronized by default meaning the value is computed only in one thread but all other threads will see the same value.
In situations where synchronization of the initialization delegate is not required to allow multiple threads to execute simultaniously. We need to 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.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
//output
computed!
Hello
Hello
Observable properties
Delegate.observable() takes two arguments
- The initial value
- A handler for modifications
The handler is called everytime you assign a property. It has three parameters.
- The property being assigned to
- The old value
- 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() {
val user = User()
user.name = "first"
user.name = "second"
}
/// output
<no name> -> first
first -> second
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.
var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue > oldValue
}
fun main() {
println(max) // 0
max = 10
println(max) // 10
max = 5
println(max) // 10
}
/// output
0
10
10
Storing properties in a map
This is useful in applications for things like: parsing Json or performing other dynamic tasks.
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
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
fun main() {
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
}
/// output
John Doe
25
Delegating to another property
A property can delegate its getter and setter to another property. This delegation is available in both top-level and class properties(member and extension).
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
We use : : qualifier in the delegate name to delegate to another property
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
Usage:
- 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.
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
}
/// output
42
That’s the end. To learn more visit the official documentation