Kotlin offers various powerful features to make code concise and efficient. One such feature is delegation, which allows you to delegate the implementation of properties or functions to another object. This concept of delegation plays a crucial role in achieving code reuse, separation of concerns, and enhancing the readability and maintainability of your code. In this blog, we will explore Kotlin delegation and delve into the details of delegated properties.
Delegated properties allow you to leverage the power of trusted helper objects called delegates. These delegates handle complex tasks, freeing up your properties to focus on their core responsibilities. From database tables to browser sessions and maps, the possibilities are endless.
Join me as we embark on this thrilling journey, exploring the art of delegation and unlocking the true potential of Kotlin delegation properties. Get ready to witness the magic as your properties become extraordinary with just a touch of delegation!
Understanding Kotlin Delegation
Delegation in programming is a design pattern where an object, known as the delegate, is given the responsibility to handle certain tasks or operations on behalf of another object, known as the delegator. The delegator object delegates the work to the delegate object, which performs the task and returns the result to the delegator.
Let’s use a real-life example to understand delegation in Kotlin. Consider a scenario where you have a restaurant with a customer, a waiter, and a chef. The customer wants to order a meal, and the waiter is responsible for taking the order and delivering it to the chef. The chef prepares the meal and hands it back to the waiter, who serves it to the customer.
In this example, the customer is the delegator, and the waiter is the delegate. The customer delegates the task of taking the order and delivering it to the waiter. The waiter performs these tasks on behalf of the customer and then delegates the task of preparing the meal to the chef. Finally, the waiter serves the meal back to the customer.
Let’s take one more example, consider a Car
class that needs to perform some operations related to engine management. Instead of implementing those operations directly in the Car
class, we can delegate them to an Engine
object. This way, the Car
class can focus on its core functionality, while the Engine
object handles engine-related tasks.
Delegation provides benefits such as modularity, maintainability, and flexibility in designing software systems.
Overview of the Delegation Pattern
The delegation pattern is a design pattern where an object delegates some or all of its responsibilities to another object. Instead of inheriting behavior, an object maintains a reference to another object and forwards method calls to it. This promotes composition over inheritance and provides greater flexibility in reusing and combining behaviors from different objects.
In Kotlin, the delegation pattern is built into the language, making it easy and convenient to implement. With the by
keyword, Kotlin allows a class to implement an interface by delegating all of its public members to a specified object. Let’s dive into the details and see how it works.
Basic Usage of Delegation in Kotlin
To understand the basic usage of delegation in Kotlin, let’s consider a simple example. Assume we have an interface called Base
with a single function print()
interface Base {
fun print()
}
Next, we define a class BaseImpl
that implements the Base
interface. It has a constructor parameter x
of type Int
and provides an implementation for the print()
function.
class BaseImpl(val x: Int) : Base {
override fun print() {
println(x)
}
}
Now, we want to create a class called Derived
that also implements the Base
interface. Instead of implementing the print()
function directly, we can delegate it to an instance of the Base
interface. We achieve this by using the by
keyword followed by the object reference in the class declaration.
class Derived(b: Base) : Base by b
In this example, the by
clause in the class declaration indicates that b
will be stored internally in objects of Derived
, and the compiler will generate all the methods of Base
that forward to b
. This means that the print()
function in Derived
will be automatically delegated to the print()
function of the b
object.
To see the delegation in action, let’s create an instance of BaseImpl
with a value of 10 and pass it to the Derived
class. Then, we can call the print()
function on the Derived
object:
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print() // Output: 10
}
When we execute the print()
function on the Derived
object, it internally delegates the call to the BaseImpl
object (b
), and thus it prints the value 10.
Overriding Methods in Delegation
In Kotlin, when a class implements an interface by delegation, it can also override methods provided by the delegate object. This allows for customization and adding additional behavior specific to the implementing class.
Let’s extend our previous example to understand method overriding in the delegation. Assume we have an interface Base
with two functions: printMessage()
and printMessageLine()
:
interface Base {
fun printMessage()
fun printMessageLine()
}
We modify the BaseImpl
class to implement the updated Base
interface with the two functions printMessage()
and printMessageLine()
:
class BaseImpl(val x: Int) : Base {
override fun printMessage() {
println(x)
}
override fun printMessageLine() {
println("$xn")
}
}
Now, let’s update the Derived
class to override the printMessage()
function:
class Derived(b: Base) : Base by b {
override fun printMessage() {
println("softAai")
}
}
In this example, the printMessage()
function in the Derived
class overrides the implementation provided by the delegate object b
. When we call printMessage()
on an instance of Derived
, it will print “softAai” instead of the original implementation.
To test the overridden behavior, we can modify the main()
function as follows:
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.printMessage() // Output: softAai
derived.printMessageLine() // Output: 10\n
}
When we call the printMessage()
function on the Derived
object, it invokes the overridden implementation in the Derived
class, and it prints “softAai” instead of 10. However, the printMessageLine()
function is not overridden in the Derived
class, so it delegates the call to the BaseImpl
object, which prints the original value 10 followed by a new line.
Property Delegation
In addition to method delegation, Kotlin also supports property delegation. This allows a class to delegate the implementation of properties to another object. Let’s understand how it works.
Assume we have an interface Base
with a read-only property message
:
interface Base {
val message: String
}
We modify the BaseImpl
class to implement the Base
interface with the message
property:
class BaseImpl(val x: Int) : Base {
override val message: String = "BaseImpl: x = $x"
}
Now, let’s update the Derived
class to delegate the Base
interface and override the message
property:
class Derived(b: Base) : Base by b {
override val message: String = "Message of Derived"
}
In this example, the Derived
class delegates the implementation of the Base
interface to the b
object. However, it overrides the message
property and provides its own implementation.
To see the property delegation in action, we can modify the main()
function as follows:
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
println(derived.message) // Output: Message of Derived
}
When we access the message
property of the Derived
object, it returns the overridden value “Message of Derived” instead of the one in the delegate object b
.
Delegated Properties in Kotlin
Delegated properties allow you to delegate the implementation of property accessors (getters and setters) to another object. This means that instead of writing the logic for accessing and setting the property directly in the class, you can delegate it to a separate class.
The general syntax for creating a delegated property is as follows:
class MyClass {
var myProperty: Type by Delegate()
}
Here, myProperty
delegates its getter and setter operations to the Delegate
object.
Property Delegates
Property delegates are classes that implement the getValue
and optionally setValue
functions. These functions are invoked when the delegated property is accessed or modified.
The getValue
function is responsible for returning the property value, and setValue
is responsible for updating the property value.
Let’s say we have a Delegate
class that will handle the logic for accessing and setting a property. The Delegate
class should have two methods: getValue()
and setValue()
. The getValue()
method retrieves the current value of the property, and the setValue()
method sets a new value for the property. These methods can be defined as either members or extensions of the Delegate
class.
Here’s an example of a Delegate
class that simply stores the value internally:
class Delegate {
private var value: Int = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
// Get the current value of the property
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
// Set a new value for the property
value = newValue
}
}
In the above code snippet, the *
is used in the parameter of the setValue()
and getValue()
methods of the Delegate
class. This syntax is called a star-projection or star-spread operator.
In Kotlin, the KProperty
interface represents a property, and it has a type parameter T
that represents the type of property. When you use the star-projection (*
) as the type argument (KProperty<*>
), it means you are using a wildcard or an unknown type for the property.
In the context of delegated properties, the KProperty<*>
parameter represents the property that is being accessed or set. The thisRef
parameter represents the instance of the class that owns the property.
By using KProperty<*>
with the star-projection, you’re saying that the property can be of any type. It allows you to create a generic Delegate
class that can handle properties of different types without explicitly specifying the type.
Also, we defined the operator
keyword, which is used to define and overload certain operators for custom types or classes.
Now, let’s create a class Foo
with a property p
that delegates its accessors to an instance of the Delegate
class:
class Foo {
var p: Int by Delegate()
var q: String by Delegate()
}
fun main() {
val foo = Foo()
foo.p = 42
foo.q = "softAai"
println(foo.p) // Output: 42
println(foo.q) // Output: softAai
}
When you create an instance of Foo
, you can access and modify the p
property as if it were a regular property. However, behind the scenes, the access and modification operations are delegated to the Delegate
class.
Let’s consider one more example for better understanding, just create a simple UpperCaseDelegate
that converts a string property to uppercase when accessed:
class UpperCaseDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value.toUpperCase()
}
}
To use this delegate:
class MyClass {
var myProperty: String by UpperCaseDelegate()
}
fun main() {
val obj = MyClass()
obj.myProperty = "resume sender app"
println(obj.myProperty) // Output: RESUME SENDER APP
}
Here, myProperty
delegates its getter operation to UpperCaseDelegate
, which converts the value to uppercase before returning it.
The “by"
keyword
The delegate object is specified after the by
keyword and can be any object that satisfies the rules of the convention for property delegates. It’s the delegate object that actually handles the logic for accessing and setting the property.
The by
keyword is used in Kotlin to indicate that a property is delegated to another object or class. When you use the by
keyword, you are specifying that the implementation of the property’s accessors (getters and setters) will be delegated to a separate delegate object.
By using the by
keyword, you make it clear that the property’s implementation is delegated to another object, enhancing code readability and allowing for better separation of concerns.
class Delegate {
private var value: Int = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
// Get the current value of the property
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
// Set a new value for the property
value = newValue
}
}
class Foo {
var p: Int by Delegate()
}
In the above example, the p
property of the Foo
class is delegated to an instance of the Delegate
class using the by
keyword. The Delegate
class provides the implementation for the property’s accessors.
So, when you access or modify the p
property of an instance of the Foo
class, the property accessors are automatically delegated to the Delegate
object. Behind the scenes, the getValue()
and setValue()
methods of the Delegate
class are called to handle the property operations.
Using the
by
keyword simplifies the syntax and makes it clear that the property behavior is delegated to another object. It promotes code reuse and separates the concerns of the owning class from the delegate class.
Standard Delegates
Kotlin provides several standard delegates in the standard library to address common scenarios. Some examples include:
lazy
: Allows for lazy initialization of properties. The initialization is deferred until the property is accessed for the first time.observable
: Enables observing property changes by providing a callback function that is triggered whenever the property value is modified.vetoable
: Allows validation of property values by providing a callback function that can reject value changes based on specific conditions.
Lazy Initialization
Lazy initialization is a technique where you delay the initialization of an object until it is accessed for the first time. This can be useful when the initialization process is resource-intensive and the object might not always be needed during the lifetime of the program.
For example, consider a Person class that lets you access a list of the emails written by a person. The emails are stored in a database and take a long time to access. You want to load the emails on first access to the property and do so only once. Let’s say you have the following function loadEmails, which retrieves the emails from the database:
class Email {
/*...*/
}
fun loadEmails(person: Person): List<Email> {
println("Load emails for ${person.name}")
return listOf(/*...*/ )
}
Here’s how you can implement lazy loading using an additional _emails property that stores null before anything is loaded and the list of emails afterward.
class Person(val name: String) {
private var _emails: List<Email>? = null
val emails: List<Email>
get() {
if (_emails == null) {
_emails = loadEmails(this)
}
return _emails!!
}
}
In this implementation, we have a nullable _emails
property that acts as a backing property to store the loaded emails. The emails
property is the one we access to retrieve the list of emails. In the getter of the emails
property, we check if _emails
is null. If it is, we initialize it by calling the loadEmails
function. We then return the value of _emails
, forcibly unwrapping it with !!
operator since we know it won’t be null at this point.
While this approach works, it can become cumbersome and error-prone when dealing with multiple lazy properties. Additionally, the implementation is not thread-safe.
To simplify and improve the code, Kotlin provides a built-in solution using the lazy
delegate. The lazy
function returns an object that has a getValue
method, which can be used together with the by
keyword to create a delegated property. Here’s how we can use it in our example:
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
With this implementation, the emails
property is delegated to the lazy
delegate. The lambda expression passed to the lazy
function is used to initialize the value of the property when it is accessed for the first time. The lazy
delegate ensures that the initialization happens only once, and subsequent accesses to the property will return the cached value.
The lazy
function is thread-safe by default, meaning that the initialization is synchronized and can be safely accessed from multiple threads. If you need more control over the thread-safety or want to optimize for a single-threaded environment, you can specify additional options to the lazy
function.
Lazy
delegate: “by lazy()”
In Kotlin, you can achieve lazy initialization using the lazy
delegate provided by the standard library. The lazy
function returns an object that has a getValue
method, which can be used together with the by
keyword to create a delegated property.
The lazy
delegate is a built-in feature in Kotlin that allows you to create properties whose values are computed lazily. It provides a concise way to implement lazy initialization without manually managing the initialization state. Here’s how you can use it:
val property: Type by lazy {
// Initialization code here
// This block will be executed only once, when the property is accessed for the first time
// The value of the block will be cached and returned for subsequent accesses
// Return the computed value
}
Here’s an example to illustrate the usage of lazy initialization with the lazy
delegate:
class Example {
val expensiveProperty: Int by lazy {
// Expensive computation or initialization
println("Initializing expensiveProperty...")
// Return the computed value
42
}
}
fun main() {
val example = Example()
println("Before accessing expensiveProperty")
// The initialization code of expensiveProperty is not executed yet
println("Value of expensiveProperty: ${example.expensiveProperty}")
// The initialization code of expensiveProperty is executed here
println("After accessing expensiveProperty")
println("Value of expensiveProperty: ${example.expensiveProperty}")
// The cached value is returned without re-initialization
}
OUTPUT
Before accessing expensiveProperty
Initializing expensiveProperty...
Value of expensiveProperty: 42
After accessing expensiveProperty
Value of expensiveProperty: 42
In this example, the expensiveProperty
in the Example
class is lazily initialized using the lazy
delegate. The initialization code block is not executed until the property is accessed for the first time. The computed value (42
in this case) is then cached and returned for subsequent accesses.
When you run the above code, you’ll see that the initialization code block is executed only once, when the property is first accessed. On subsequent accesses, the cached value is returned without re-executing the initialization code.
Lazy initialization with by lazy()
simplifies the code by abstracting away the details of managing the initialization state and caching the computed value. It ensures that the property is initialized lazily and provides a convenient way to implement lazy initialization in Kotlin.
Once again here’s an example to illustrate lazy initialization using the lazy
delegate:
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
In this example, the Person
class has a property called emails
, which is lazily initialized using the lazy
delegate. The lazy
function takes a lambda as an argument, which it will call to initialize the value of the property when it is accessed for the first time.
The benefit of using the lazy
delegate is that the initialization logic is encapsulated within it. The value assigned to the emails
property will only be computed once, on the first access, and subsequent accesses will return the cached value. This can help improve performance by avoiding unnecessary computations or resource allocations until they are actually needed.
You can think of the emails
property as having a backing property that holds the computed value, and the lazy
delegate takes care of initializing and caching the value behind the scenes. The delegate ensures that the value is computed lazily, i.e., only when it is first accessed.
Here’s how you would use the Person
class:
val person = Person("amol")
println(person.emails) // Initialization happens here, loadEmails() is called
println(person.emails) // Cached value is returned without re-initialization
In this example, the loadEmails()
function will only be called on the first access of the emails
property. Subsequent accesses will return the cached value without re-initializing it.
The lazy
delegate is thread-safe by default, meaning that the initialization is synchronized and can be safely accessed from multiple threads. However, if you know that the class will only be used in a single-threaded environment, you can provide additional options to bypass synchronization and improve performance.
The
lazy
delegate allows you to achieve lazy initialization of properties. It simplifies the code by encapsulating the initialization logic and ensures that the value is computed only when it is first accessed, providing better performance and resource utilization.
"observable”
Delegate
The observable
delegate allows you to observe property changes by providing a callback function that is triggered whenever the property value is modified.
Here’s the general syntax for using the observable
delegate:
var propertyName: Type by Delegates.observable(initialValue) { property, oldValue, newValue ->
// Callback function logic
}
Let’s see an example that uses the observable
delegate to observe changes in a property:
import kotlin.properties.Delegates
class Person {
var age: Int by Delegates.observable(25) { property, oldValue, newValue ->
println("Age changed from $oldValue to $newValue")
}
}
fun main() {
val person = Person()
person.age = 30 // Output: Age changed from 25 to 30
person.age = 35 // Output: Age changed from 30 to 35
}
In this example, the age
property is observed using the observable
delegate. Whenever the age
property is modified, the callback function is triggered, printing the old value and the new value.
"vetoable”
Delegate
The vetoable
delegate allows you to validate property values by providing a callback function that can reject value changes based on specific conditions.
Here’s the general syntax for using the vetoable
delegate:
var propertyName: Type by Delegates.vetoable(initialValue) { property, oldValue, newValue ->
// Validation logic
// Return true to accept the new value, or false to reject it
}
Let’s see an example that uses the vetoable
delegate to validate a property value:
import kotlin.properties.Delegates
class Circle {
var radius: Double by Delegates.vetoable(0.0) { property, oldValue, newValue ->
newValue >= 0.0 // Only accept positive or zero radius values
}
}
fun main() {
val circle = Circle()
circle.radius = 5.0
println(circle.radius) // Output: 5.0
circle.radius = -2.0 // Value rejected due to validation
println(circle.radius) // Output: 5.0 (unchanged)
}
In this example, the radius
property is validated using the vetoable
delegate. The callback function checks if the new value is greater than or equal to zero. If the validation condition is not met (e.g., negative radius), the value change is rejected, and the property retains its previous value.
Delegating to another property
Delegating a property to another property means that the getter and setter of one property are implemented by accessing or modifying another property’s value. This delegation can be done for top-level properties, member properties (including extension properties) within the same class, or even member properties of another class.
To delegate a property to another property, you use the ::
qualifier followed by the delegate property’s name. Here are a few examples to illustrate how property delegation works:
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
In the code above, we have different scenarios for property delegation:
delegatedToMember
is a property within theMyClass
class that delegates its getter and setter to thememberInt
property of the same class. This means that accessing or modifyingdelegatedToMember
will actually read from or write tomemberInt
.delegatedToTopLevel
is a property within theMyClass
class that delegates its getter and setter to the top-level propertytopLevelInt
. So, accessing or modifyingdelegatedToTopLevel
will actually read from or write totopLevelInt
.delegatedToAnotherClass
is a property within theMyClass
class that delegates its getter to theanotherClassInt
property of an instance ofClassWithDelegate
. This means that accessingdelegatedToAnotherClass
will read the value ofanotherClassInstance.anotherClassInt
.extDelegated
is an extension property ofMyClass
that delegates its getter and setter to the top-level propertytopLevelInt
. This allows instances ofMyClass
to have an additional propertyextDelegated
that shares its value withtopLevelInt
.
Property delegation can be useful in various scenarios. One common use case is when you want to introduce a new property while maintaining backward compatibility with an existing one. In such cases, you can introduce a new property, annotate the old property with the @Deprecated
annotation, and delegate its implementation to the new property. Here’s an example:
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) // Output: 42
}
In this example, we have a class MyClass
with oldName
and newName
properties. The oldName
property is deprecated and annotated with @Deprecated
, indicating that it should not be used anymore. The implementation of oldName
is delegated to the newName
property using by this::newName
. So, accessing or modifying oldName
will actually access or modify the newName
property.
In the main
function, we demonstrate the usage of the deprecated oldName
property. When assigning a value to oldName
, a deprecation warning is displayed. However, the value is stored in the newName
property, which can be accessed correctly.
Overall, delegating properties to other properties provides a powerful mechanism to reuse existing property implementations, introduce backward compatibility, and simplify property access and modification.
Property delegate requirements
Property delegate requirements will be demonstrated for both read-only (val) and mutable (var) properties. Let’s break down the concept of delegated properties for read-only and mutable properties (var) and understand how to provide the necessary operator functions for delegation.
For a read-only property (val), the delegate must provide the getValue()
operator function with the following parameters:
thisRef
: This parameter should be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).property
: This parameter should be of typeKProperty<*>
or its supertype.getValue()
: This function must return the same type as the property (or its subtype).
Here’s an example:
class Resource
class Owner {
val valResource: Resource by ResourceDelegate()
}
class ResourceDelegate {
operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
return Resource()
}
}
In this code, we have the Owner
class with a read-only property valResource
. The delegation is done by using the by
keyword and providing an instance of the ResourceDelegate
class. The ResourceDelegate
class defines the getValue()
function, which returns an instance of Resource
. The function receives the property owner (thisRef
) and the KProperty<*>
instance representing the property being delegated.
For a mutable property (var), in addition to the getValue()
function, the delegate must provide the setValue()
operator function with the following parameters:
thisRef
: This parameter should be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).property
: This parameter should be of typeKProperty<*>
or its supertype.value
: This parameter should be of the same type as the property (or its supertype).
Here’s an example:
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
}
}
}
In this code, we have the Owner
class with a mutable property varResource
. The ResourceDelegate
class now includes the setValue()
function, which allows modifying the value of the delegated property. The function receives the property owner (thisRef
), the KProperty<*>
instance representing the property being delegated, and the new value to be assigned.
You can define the getValue()
and setValue()
functions as member functions of the delegate class itself or as extension functions. Both functions need to be marked with the operator
keyword to enable operator overloading.
Alternatively, you can create delegates as anonymous objects using the interfaces ReadOnlyProperty
and ReadWriteProperty
from the Kotlin standard library. These interfaces provide the required getValue()
and setValue()
methods. By using anonymous objects, you can avoid creating separate classes for the delegates. Here’s an example:
fun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty<Any?, Resource> =
object : ReadWriteProperty<Any?, Resource> {
private var curValue = resource
override fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValue
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
curValue = value
}
}
val readOnlyResource: Resource by resourceDelegate() // ReadWriteProperty used as a read-only property
var readWriteResource: Resource by resourceDelegate() // ReadWriteProperty used as a mutable property
In this code, the resourceDelegate()
function returns an anonymous object implementing the ReadWriteProperty
interface. The ReadWriteProperty
interface extends ReadOnlyProperty
, so it can be used as a delegate for both read-only and mutable properties. The anonymous object defines the necessary getValue()
and setValue()
functions.
By using delegated properties and providing the appropriate operator functions, you can create flexible and reusable property delegation patterns in Kotlin.
Storing property values in a map
Another common pattern where delegated properties come into play is objects that have a dynamically defined set of attributes associated with them. Such objects are sometimes called expando objects. in a contact-management system, each person may have some required properties (like name) that are handled in a special way, as well as additional attributes that can vary for each person(youngest child’s birthday, for example).
One way to implement such a system is by using a map to store all the attributes of a person and providing properties that allow access to the information with special handling. Let’s go through the code examples to understand this approach.
First, we have the Person
class with a private _attributes
map. This map will store the attributes of a person, where the keys are attribute names and the values are attribute values.
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
val name: String
get() = _attributes["name"]!!
}
In this code, we have a set attribute
function that allows adding or updating attributes in the _attributes
map. The name
property is an example of a required property that is handled in a special way. It retrieves the value of the “name” attribute from the _attributes
map.
To create an instance of the Person
class and load data into it, we can use a generic API, such as deserialization from JSON, as shown in the example below:
val p = Person()
val data = mapOf("name" to "amol", "company" to "softAai")
for ((attrName, value) in data) {
p.setAttribute(attrName, value)
}
println(p.name) // Output: amol
Here, we create a new Person
instance and provide the data as a map. We iterate over each key-value pair in the data map and call setAttribute
to store the attributes in the _attributes
map. Finally, we can access the name
property, which internally retrieves the value of the “name” attribute from the _attributes
map.
Now, instead of manually implementing the property and the _attributes
map, we can simplify the code using delegated properties. We can directly delegate the name
property to the _attributes
map using the by
keyword, as shown below:
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
val name: String by _attributes
}
In this code, we no longer have the explicit getter for the name
property. Instead, we use the by
keyword to delegate the property to the _attributes
map. The standard library provides getValue
and setValue
extension functions for maps, allowing the property to automatically get and set the values in the map based on the property name.
With the delegated property in place, we can use it just like before:
val p = Person()
val data = mapOf("name" to "amol", "company" to "softAai")
for ((attrName, value) in data) {
p.setAttribute(attrName, value)
}
println(p.name) // Output: amol
The output remains the same, but now the name
property is implemented as a delegated property, simplifying the code and removing the need for an explicit getter.
Delegated properties provide a concise and reusable way to handle dynamically defined attributes in expando objects. By leveraging the
by
keyword and the standard library extension functions, we can delegate the property access to a map or any other custom logic, making the code more maintainable and flexible.
Translation rules for delegated properties
When using delegated properties in Kotlin, the Kotlin compiler generates auxiliary properties to handle the delegation. These auxiliary properties are used to store the delegate object and manage the getter and setter operations.
Let’s take an example to understand how this works. Consider the following code:
class C {
var prop: Type by MyDelegate()
}
When the compiler encounters this code, it generates a hidden property called prop$delegate
. This hidden property is of the same type as the delegate class (MyDelegate
in this case). It is responsible for handling the delegation of the prop
property.
The generated code looks like this:
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)
}
In the generated code, the prop
property has a getter and a setter. The getter delegates the getValue()
operation to the prop$delegate
property, passing the instance of the outer class (this
) and the reflection object (this::prop
) that represents the property itself. The delegate’s getValue()
function is responsible for providing the value of the property.
Similarly, the setter delegates the setValue()
operation to the prop$delegate
property, passing the instance of the outer class (this
), the reflection object (this::prop
), and the new value of the property. The delegate’s setValue()
function handles the assignment of the new value.
By generating the
prop$delegate
property and delegating to it, the compiler ensures that the getter and setter operations are correctly handled by the delegate object (MyDelegate
).
Optimized cases for delegated properties
When it comes to optimization, the Kotlin compiler can omit the $delegate
field in certain cases. Here are the optimized cases for delegated properties:
A referenced property:
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
In this case, the property prop
is delegated to another property impl
using the by
keyword. Since the delegate is a referenced property within the same class, the compiler can optimize the generated code and omit the $delegate
field. Instead, the accessors directly delegate to the referenced property impl
.
A named object:
object NamedObject {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}
val s: String by NamedObject
When using a named object as a delegate, the Kotlin compiler can optimize the code and omit the $delegate
field. The accessors directly call the delegate’s getValue
function without the need for an intermediate property.
A 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
}
In this case, the delegate impl
is a final val
property with a backing field and a default getter defined in the same module as the property s
. The compiler can optimize the code and omit the $delegate
field. The accessors directly delegate to the getValue
function of the delegate without the need for an intermediate property.
A constant expression, enum entry, this
, or null
:
class A {
operator fun getValue(thisRef: Any?, property: KProperty<*>) ...
val s by this
}
If the delegate is a constant expression, an enum entry, this
, or null
, the Kotlin compiler can optimize the code and omit the $delegate
field. The accessors directly call the getValue
function of the delegate without the need for an intermediate property.
In these optimized cases, the compiler eliminates the need for the $delegate
field, which can save memory and provide more efficient property access. The accessors directly invoke the corresponding functions of the delegate, leading to more streamlined code execution.
Note that these optimizations are applied by the Kotlin compiler to improve performance and reduce unnecessary overhead when using delegated properties.
Translation rules when delegating to another property
When delegating to another property, the Kotlin compiler optimizes the code by generating immediate access to the referenced property. This means that the compiler doesn’t generate the $delegate
field. This optimization helps save memory and improves performance.
Let’s take a look at the example code:
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
In this case, the property prop
is delegated to the property impl
using the by
keyword. The compiler optimizes the code by directly accessing the impl
property within the property accessors of prop
. This means that the delegated property’s getValue
and setValue
operators are skipped, and there is no need for the KProperty
reference object.
The compiler generates the following code:
class C<Type> {
private var impl: Type = ...
var prop: Type
get() = impl
set(value) {
impl = value
}
fun getProp$delegate(): Type = impl // This method is needed only for reflection
As you can see, the accessors for the prop
property directly delegate to the impl
property. The getValue
accessor returns the value of impl
, and the setValue
accessor assigns the value to impl
.
The method getProp$delegate()
is also generated, but it is only needed for reflection purposes. It allows reflective access to the delegate object, but it is not used in regular property access.
This optimization avoids the creation of an additional field and reduces the overhead associated with delegated property access. By directly accessing the referenced property, the code becomes more efficient and memory-friendly.
The same optimization principle applies when delegating to another property using this
keyword. The compiler generates immediate access to the referenced property without the need for an intermediate field.
Overall, these translation rules improve the performance of delegated properties and eliminate unnecessary memory usage.
What will be the right-hand side of “by"?
In Kotlin, when using delegated properties, the expression to the right of the by
keyword can be more than just a new instance creation. It can also be a function call, another property, or any other expression, as long as the value of the expression is an object that provides the getValue
and setValue
methods with the correct parameter types.
The getValue
and setValue
methods can be declared directly on the object itself or defined as extension functions. This gives you the flexibility to use existing functions or properties to handle the behavior of your delegated properties.
Here’s an example to demonstrate this concept:
class Example {
var value: String = "initial"
// Delegated property using a function call
var customValue: String by getValueFromFunction()
// Delegated property using another property
var anotherValue: String by ::value
// Delegated property using an extension function
var computedValue: Int by calculateValue()
// Extension function providing delegated property behavior
private fun calculateValue(): ReadWriteProperty<Any?, Int> {
var storedValue: Int = 0
return object : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
// Perform custom logic to compute the value
return storedValue * 2
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
// Perform custom logic to store the value
storedValue = value / 2
}
}
}
}
fun getValueFromFunction(): ReadWriteProperty<Any?, String> {
var storedValue: String = ""
return object : ReadWriteProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return storedValue
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
storedValue = value.toUpperCase()
}
}
}
fun main() {
val example = Example()
// Using the delegated properties
example.customValue = "amol"
println(example.customValue) // Output: AMOL
example.anotherValue = "softAai"
println(example.anotherValue) // Output: softAai
example.computedValue = 5
println(example.computedValue) // Output: 10
}
In the example above, the customValue
property is delegated to the result of a function call getValueFromFunction()
, which returns a custom delegate object implementing the ReadWriteProperty
interface. The delegate modifies the stored value by converting it to uppercase when setting the value.
The anotherValue
property is delegated to the value
property using the ::value
syntax. Any changes made to anotherValue
will be reflected in the value
property.
The computedValue
property is delegated to the result of the calculateValue()
extension function. The extension function provides the delegated property behavior by implementing the ReadWriteProperty
interface. In this case, the delegate computes the value by multiplying it by 2 and stores the value by dividing it by 2.
By allowing various expressions on the right-hand side of
by
and supporting both object-defined and extension-definedgetValue
andsetValue
methods, Kotlin enables flexible and customizable behavior for delegated properties.
Providing a delegate
The provideDelegate
operator allows you to extend the logic for creating the object to which the property implementation is delegated. If the object used on the right-hand side of the by
keyword defines provideDelegate
as a member or extension function, that function will be called to create the property delegate instance.
One use case of provideDelegate
is to perform additional checks or actions during the initialization of the property delegate. For example, you can check the consistency of the property before binding it.
Here’s an example that demonstrates how to use provideDelegate
:
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)
}
In this example, the ResourceLoader
class defines the provideDelegate
function. This function is called for each property during the creation of an instance of MyUI
. It performs the necessary validation or checks before creating the property delegate.
Without the provideDelegate
functionality, you would need to pass the property name explicitly to achieve the same functionality, which could be less convenient.
The provideDelegate
method has the same parameters as the getValue
function:
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 responsible for creating and returning the property delegate instance that will handle the property access.
In the generated code, when provideDelegate
is present, it 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 when provideDelegate
is available:
class C {
var prop: Type by MyDelegate()
}
// Generated code with `provideDelegate` function:
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)
}
It’s important to note that the
provideDelegate
method only affects the creation of the auxiliary property (prop$delegate
) and does not impact the generated code for the getter or the setter of the delegated property.
You can also use the PropertyDelegateProvider
interface from the standard library to create delegate providers without creating new classes. Here’s an example:
val provider = PropertyDelegateProvider { thisRef: Any?, property ->
ReadOnlyProperty<Any?, Int> { _, property -> 42 }
}
val delegate: Int by provider
In this case, the PropertyDelegateProvider
creates a delegate provider using a lambda expression. The lambda receives the thisRef
(property owner) and property
information and returns a property delegate instance.
Delegation in Android Applications
Kotlin delegation and delegated properties can be useful in Android applications to simplify code, separation of concerns, and provide flexibility in handling certain tasks. Here are a few examples of how delegation can be used in Android applications:
1. Shared Preferences Delegation
Android applications often need to store and retrieve key-value pairs using SharedPreferences. Delegation can be used to simplify the code for accessing SharedPreferences. For example:
class SettingsManager(context: Context) {
private val preferences: SharedPreferences by lazy {
context.getSharedPreferences("settings", Context.MODE_PRIVATE)
}
var isNotificationsEnabled: Boolean by BooleanPreferenceDelegate(
preferences, "notifications_enabled", true
)
}
class BooleanPreferenceDelegate(
private val preferences: SharedPreferences,
private val key: String,
private val defaultValue: Boolean
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return preferences.getBoolean(key, defaultValue)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
preferences.edit { putBoolean(key, value) }
}
}
In this example, the SettingsManager
class uses delegation to handle the isNotificationsEnabled
property, which is backed by a shared preference value. The BooleanPreferenceDelegate
class implements the delegated property behavior.
2. Dependency Injection with Delegation:
Dependency injection frameworks like Dagger can benefit from delegation to simplify the injection process. By using a delegated property, you can abstract away the complexity of dependency resolution. Here’s a simplified example:
class MyActivity : AppCompatActivity() {
private val myDependency: MyDependency by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use myDependency
}
}
interface Injectable {
fun injectDependencies()
}
class MyDependency {
// Dependency implementation
}
inline fun <reified T : Injectable> AppCompatActivity.inject(): Lazy<T> {
return lazy {
val injectable = T::class.java.newInstance()
injectable.injectDependencies()
injectable
}
}
In this example, the MyActivity
class uses delegation to lazily inject the MyDependency
instance. The inject
function provides the delegation logic for dependency injection, making it easy to reuse across different activities.
These are just a few examples of how delegation and delegated properties can be used in Android applications. They demonstrate how delegation can simplify code, improve code organization, and provide flexibility in various scenarios.
Conclusion
Kotlin delegation and delegated properties provide an elegant and efficient way to handle code reuse and separation of concerns. By understanding the concept of delegation and utilizing delegated properties, you can write cleaner, more maintainable code in your Kotlin projects. Whether you’re developing Android applications or working on other Kotlin projects, delegation is a powerful tool to enhance your codebase. Start exploring the possibilities of delegation in Kotlin and unlock the benefits it brings to your software development journey.