Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
27: Extension Properties
I was working on a project yesterday where I had a User class defined in a shared library. I couldn't touch the source code of that library, but I found myself constantly concatenating the first and last names of the user throughout my UI code. It felt messy. I wanted a fullName property on the User object, but since I can't modify the class, I figured I'd try an extension property.
Trying the intuitive way
My first instinct was to just declare the property and assign it a value, similar to how I would inside a class. I tried this:
class User(val firstName: String, val lastName: String)
// I want to add this to the User class from the outside
val User.fullName = "${firstName} ${lastName}"
fun main() {
val user = User("Jane", "Doe")
println(user.fullName)
}
The compiler immediately yelled at me. It told me that extension properties cannot have backing fields. This is a crucial distinction. When you add a property to a class via an extension, you aren't actually adding a new field to the memory layout of that object. You're not adding a "slot" to store data in the User instance. If Kotlin allowed this, it would be magic that breaks how the JVM actually handles objects.
Solving it with a getter
So, if I can't store a value, what can I do? I remembered that properties in Kotlin are essentially just functions in disguise. A val is really just a get() method. I decided to pivot and explicitly define the getter instead of trying to assign a value.
class User(val firstName: String, val lastName: String)
val User.fullName: String
get() = "$firstName $lastName"
fun main() {
val user = User("Jane", "Doe")
// Now this works perfectly
println(user.fullName)
}
Now it works. When I call user.fullName, Kotlin isn't looking up a variable in memory; it's executing that get() block. It feels like a property to the person using the class, but under the hood, it's just a function that takes the User instance as a receiver.
What about setters?
I wondered if I could go a step further. What if I wanted to be able to update the firstName and lastName simultaneously using a fullName setter? I tried to implement a var extension property:
var User.fullName: String
get() = "$firstName $lastName"
set(value) {
val parts = value.split(" ")
// Wait, this only works if firstName and lastName are vars!
}
I hit another wall. Since my User class used val for the names, I couldn't change them. But even if they were var, I realized that extension properties are purely "computed." They can't hold their own state. If you need to store a new piece of data that isn't already present in the original class, an extension property is the wrong tool—you'd need a wrapper class or a proper inheritance hierarchy.
In short: use extension properties when you want to derive a value from existing data in a way that feels natural to the API, but don't expect them to behave like actual fields in the class.
📋 Practical Task
Implement a String-based Word Counter
You are building a text analysis tool. Instead of writing a utility function like countWords(text), you want to make the code more readable by adding an extension property to the String class.
Your Task:
- Create an extension property called
wordCountfor theStringclass. - The property should be a
val. - The getter should split the string by whitespace and return the number of resulting elements.
- In your
mainfunction, create a sentence (e.g.,"Kotlin is an expressive language") and print itswordCount.
There are no comments for now.