Standard delegates are powerful features provided by the language to simplify property management. The main standard delegates are lazy
, observable
, and vetoable
and notNull
. Each serves a distinct purpose, helping manage property initialization, changes, and validations more effectively. Let’s explore these with examples and explanations.
1. Lazy Delegation
The lazy
delegate is used for lazy initialization. The property value is computed only once when it is accessed for the first time. The property is not initialized when the object is created. Instead, it is initialized the first time it is accessed.
This can save time and memory, especially if the property is expensive to create or might never be used. By default, lazy
is thread-safe, meaning it ensures the property is initialized only once, even if accessed from multiple threads.
val myValue: String by lazy {
println("Computed!")
"Hello, World!"
}
Explanation:
- Initialization: The block of code inside
lazy
is executed only once, the first timemyValue
is accessed. - Thread Safety: By default,
lazy
is thread-safe and uses a synchronized block to ensure only one thread initializes the value. There are different modes for thread safety likeLazyThreadSafetyMode.PUBLICATION
andLazyThreadSafetyMode.NONE
.
Usage:
fun main() {
println(myValue) // Prints "Computed!" followed by "Hello, World!"
println(myValue) // Prints "Hello, World!" (value is not recomputed)
}
Output
Computed!
Hello, World!
Hello, World!
2. Observable Delegation
The observable
delegate allows you to react to changes in a property. It takes an initial value and a callback that gets invoked each time the property is assigned a new value.
Change Tracking: It allows you to execute a block of code whenever the property value changes. This is useful for debugging, logging, or updating the UI in response to property changes.
Automatic Notification: It Simplifies the process of reacting to property changes without manually adding boilerplate code.
import kotlin.properties.Delegates
var myObservableProperty: String by Delegates.observable("Initial Value") { property, oldValue, newValue ->
println("Property '${property.name}' changed from '$oldValue' to '$newValue'")
}
Explanation:
- Initialization: The property starts with the initial value “Initial Value”.
- Callback: The lambda function is called every time the property is updated, allowing you to react to the change.
Usage:
fun main() {
myObservableProperty = "New Value" // Triggers the callback
myObservableProperty = "Another Value" // Triggers the callback again
}
Output:
Property 'myObservableProperty' changed from 'Initial Value' to 'New Value'
Property 'myObservableProperty' changed from 'New Value' to 'Another Value'
3. Vetoable Delegation
The vetoable
delegate allows you to veto changes to a property. It takes an initial value and a callback that determines whether the new value should be accepted or not.
- Validation: Allows you to enforce constraints on property values before they are set. This helps maintain the integrity of the data and prevents invalid values from being assigned.
- Custom Logic: Provides a mechanism to include custom validation logic in property assignments.
import kotlin.properties.Delegates
var myVetoableProperty: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue >= 0 // Only accept non-negative values
}
Explanation:
- Initialization: The property starts with the initial value
0
. - Callback: The lambda function is called each time the property is updated. If the function returns
true
, the new value is accepted; otherwise, it is rejected.
Usage:
fun main() {
myVetoableProperty = 10
println(myVetoableProperty) // Prints: 10
myVetoableProperty = -5
println(myVetoableProperty) // Prints: 10 (change is vetoed)
}
4. NotNull Delegation
The notNull
delegate allows you to declare a property that should not be null
and throws an exception if accessed before being initialized. This is useful for properties that are supposed to be initialized later, for example, in dependency injection or in init
blocks.
import kotlin.properties.Delegates
var myNotNullProperty: String by Delegates.notNull<String>()
Explanation:
- Non-null Property: Ensures the property cannot be accessed before being initialized.
- Exception on Access: Throws an
IllegalStateException
if accessed before initialization.
fun main() {
try {
println(myNotNullProperty) // Throws an exception
} catch (e: IllegalStateException) {
println("Caught exception: ${e.message}")
}
myNotNullProperty = "Initialized Value"
println(myNotNullProperty) // Prints: Initialized Value
}
Output:
Caught exception: Property myNotNullProperty should be initialized before get.
Initialized Value
These delegates can significantly reduce boilerplate code and improve readability and maintainability in your Kotlin applications.
Also Read :