Kotlin Extension Properties
Introduction
Extension properties are a powerful feature in Kotlin that allows developers to add new properties to existing classes without having to inherit from them or modify their source code. This feature builds on Kotlin's extension functions concept but applies specifically to properties instead of functions.
Extension properties can be particularly useful when working with classes from libraries or frameworks where you cannot modify the original source code, but you want to add convenient properties to enhance readability and maintainability.
Understanding Extension Properties
What Are Extension Properties?
Extension properties allow you to define new properties for existing classes. They work in a similar way to extension functions but provide a property-like access syntax.
The basic syntax for defining an extension property is:
val ClassName.propertyName: Type
get() {
// Property getter implementation
return something
}
var ClassName.propertyName: Type
get() {
// Property getter implementation
return something
}
set(value) {
// Property setter implementation
}
Key Characteristics
- No Actual Storage: Extension properties do not actually add fields to the extended class - they can only use custom getters and setters
- No Backing Field: Unlike regular properties, extension properties cannot have backing fields
- Accessibility: They can access public members of the extended class
- Static Resolution: Extension properties are statically resolved, based on the declared type of the variable
Basic Extension Property Examples
Let's start with a simple example to demonstrate how to create and use extension properties:
// Adding a 'lastIndex' property to String class
val String.lastIndex: Int
get() = this.length - 1
fun main() {
val message = "Hello Kotlin"
println("The last index of '$message' is: ${message.lastIndex}")
// Accessing the character at the last index
println("The last character is: ${message[message.lastIndex]}")
}
Output:
The last index of 'Hello Kotlin' is: 11
The last character is: n
In this example, we've added a lastIndex
property to the String
class that returns the index of the last character. This makes our code more readable compared to writing message.length - 1
each time.
Mutable Extension Properties
Extension properties can also be mutable using the var
keyword:
class Person(var firstName: String, var lastName: String)
var Person.fullName: String
get() = "$firstName $lastName"
set(value) {
val parts = value.split(" ")
if (parts.size == 2) {
firstName = parts[0]
lastName = parts[1]
} else {
throw IllegalArgumentException("Full name should consist of first name and last name")
}
}
fun main() {
val person = Person("John", "Doe")
println("Full name: ${person.fullName}")
// Using the setter
person.fullName = "Jane Smith"
println("Updated full name: ${person.fullName}")
println("First name: ${person.firstName}")
println("Last name: ${person.lastName}")
}
Output:
Full name: John Doe
Updated full name: Jane Smith
First name: Jane
Last name: Smith
In this example, we've added a mutable fullName
property to the Person
class. The getter returns the concatenated first and last names, while the setter splits the full name and updates the individual parts.
Extension Properties with Generics
Extension properties can also be defined for generic types:
val <T> List<T>.secondOrNull: T?
get() = if (this.size >= 2) this[1] else null
fun main() {
val numbers = listOf(10, 20, 30, 40)
val emptyList = emptyList<Int>()
println("Second element of numbers: ${numbers.secondOrNull}")
println("Second element of emptyList: ${emptyList.secondOrNull}")
val names = listOf("Alice", "Bob", "Charlie")
println("Second name: ${names.secondOrNull}")
}
Output:
Second element of numbers: 20
Second element of emptyList: null
Second name: Bob
Here, we've created a secondOrNull
extension property for any List<T>
that safely returns the second element or null if the list doesn't have enough elements.
Practical Applications
Example 1: Date Extensions
Extension properties can make working with dates more intuitive:
import java.util.Date
import java.text.SimpleDateFormat
import java.util.Calendar
val Date.formattedDate: String
get() = SimpleDateFormat("yyyy-MM-dd").format(this)
val Date.isToday: Boolean
get() {
val today = Calendar.getInstance()
val thisDay = Calendar.getInstance().apply { time = this@isToday }
return today.get(Calendar.YEAR) == thisDay.get(Calendar.YEAR) &&
today.get(Calendar.MONTH) == thisDay.get(Calendar.MONTH) &&
today.get(Calendar.DAY_OF_MONTH) == thisDay.get(Calendar.DAY_OF_MONTH)
}
fun main() {
val date = Date()
println("Current date formatted: ${date.formattedDate}")
println("Is this date today? ${date.isToday}")
}
Output:
Current date formatted: 2023-05-20
Is this date today? true
Example 2: View Extensions in Android
If you're developing Android applications, extension properties can significantly improve code readability:
// Example of Android extension properties
import android.view.View
import android.widget.EditText
// This would be in an Android project
val View.visible: Boolean
get() = visibility == View.VISIBLE
set(value) {
visibility = if (value) View.VISIBLE else View.GONE
}
val EditText.isEmpty: Boolean
get() = text.toString().trim().isEmpty()
// Usage would be like:
// myButton.visible = false
// if (emailField.isEmpty) { showError() }
Example 3: Working with Collections
Extension properties can make working with collections more convenient:
val <T> Collection<T>.isNotEmpty: Boolean
get() = !isEmpty()
val <T> List<T>.lastOrNull: T?
get() = if (isEmpty()) null else this[size - 1]
fun main() {
val numbers = listOf(1, 2, 3)
val emptyList = listOf<String>()
if (numbers.isNotEmpty) {
println("Numbers list is not empty with ${numbers.size} elements")
}
println("Last element or null: ${numbers.lastOrNull}")
println("Last element of empty list: ${emptyList.lastOrNull}")
}
Output:
Numbers list is not empty with 3 elements
Last element or null: 3
Last element of empty list: null
Limitations and Best Practices
While extension properties are powerful, they come with some limitations:
- No backing field: As mentioned, extension properties cannot have backing fields
- Initialization: They cannot have initializers
- Delegation: You cannot use property delegates with extension properties
- Visibility: Extension properties are resolved statically, so they cannot override properties in the original class
Best Practices:
- Use for computed properties: Extension properties work best for computed values
- Enhance existing classes: Use them to add domain-specific properties to library classes
- Clear naming: Choose names that clearly communicate what the property represents
- Keep it simple: Avoid complex logic in property getters and setters
- Documentation: Document extension properties well, especially if they're in a utility class
Summary
Extension properties in Kotlin provide a powerful way to extend existing classes with new properties without modifying their source code or using inheritance. They are particularly useful for:
- Adding computed properties to existing classes
- Enhancing classes from libraries or the standard library
- Improving code readability with domain-specific properties
- Creating utility properties for common operations
While they have limitations like the inability to have backing fields, these constraints ensure that extension properties remain a lightweight addition to the language without compromising its performance or type safety.
Exercises
- Create an extension property
isPalindrome
forString
that returns a boolean indicating whether the string reads the same backward as forward. - Add an extension property
initials
to aPerson
class that returns the first letter of the first name and last name. - Create an extension property
isWeekend
forjava.time.LocalDate
that returns true if the date falls on Saturday or Sunday. - Add an extension property
averageOrNull
toList<Int>
that returns the average of all elements or null if the list is empty. - Create a
square
extension property forInt
that returns the square of the number.
Additional Resources
- Kotlin Official Documentation on Extensions
- Kotlin Standard Library Extensions
- Effective Kotlin by Marcin Moskala - Contains advanced patterns using extensions
- Clean Code in Kotlin - Best practices including usage of extension properties
Happy coding with Kotlin extension properties!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)