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
107: Reflecting Over Properties and Functions
When you first dive into kotlin-reflect, it's easy to fall into the trap of thinking that because you've grabbed a reference to a member of a class, you can just "run" it. I see this all the time: a developer gets a list of members from a KClass, finds the function they want, and tries to call it as if it were a standalone lambda.
Thinking members can be called without a receiver / Providing the instance to KCallable.call()
Here is the mistake. Imagine you have a simple class representing a game character:
class GameCharacter(val name: String) {
fun shout() = "I am $name!"
}
A common misconception is that you can do something like this to trigger the shout:
val shoutFunc = GameCharacter::class.members.find { it.name == "shout" }
shoutFunc?.call() // ❌ This will throw an IllegalArgumentException
Why does this fail? Because shout() is an instance method. It needs an actual GameCharacter object to operate on. The reflection object shoutFunc is just the blueprint of the function; it isn't bound to any specific character. I like to think of it as having the instructions for a car's horn but no actual car to honk.
To fix this, you have to pass the instance (the "receiver") as the first argument to the call method. This is how you actually bridge the gap between the metadata and the execution:
val hero = GameCharacter("Arthur")
val shoutFunc = GameCharacter::class.members.find { it.name == "shout" }
shoutFunc?.call(hero) // ✅ Prints "I am Arthur!"
Confusing a KProperty object with the value it holds / Using KProperty.get() to retrieve data
The same mental hurdle exists with properties. Beginners often treat a KProperty object as if it were the variable itself. If you're iterating through the properties of a class, you aren't looking at the data; you're looking at the definition of that data.
If you try to print a property found via reflection, you'll get something like kotlin.reflect.jvm.KPropertyImpl instead of the actual value. This is because a property in Kotlin is more than just a field—it's a combination of a getter and a setter.
To get the actual value, you have to use the getter or the call method, again passing in the instance you want to inspect:
val hero = GameCharacter("Arthur")
val nameProp = GameCharacter::class.members.find { it.name == "name" } as KProperty1<GameCharacter, *>
// This gives you the "Property" object
println(nameProp)
// This gives you "Arthur"
println(nameProp.get(hero))
Keep in mind that KProperty1 is used specifically for properties of a class that take one receiver (the instance). If you're working with top-level properties, you'd use KProperty0. It's a bit of a naming quirk in the library, but it essentially tells you how many arguments the "getter" needs to function.
KFunction: Usecall()and remember to include the instance as the first argument for member functions.KProperty: Useget()(forKProperty1) to extract the actual value from a specific instance.members: This collection contains both functions and properties, which is why you often see a lot of casting (as KProperty) when reflecting over a class.
📋 Practical Task
Build a Dynamic Stat Modifier
You are building a RPG system where "Power-ups" can modify any property of a character, but the Power-up doesn't know which property it's modifying until runtime. This requires you to use reflection to find and update a property by its string name.
Complete the StatModifier class. It should have a function modifyStat(target: Player, statName: String, newValue: Int) that looks up the property by name and updates its value.
Requirements:
- The
Playerclass hasvar strength: Intandvar agility: Int. - You must use
Player::class.membersto find the property matchingstatName. - You must cast the member to a
KMutableProperty1<Player, Int>to use thesetter. - Handle the case where the
statNameprovided doesn't exist in thePlayerclass (e.g., by printing "Stat not found").
import kotlin.reflect.*
import kotlin.reflect.full.*
class Player(var strength: Int, var agility: Int)
class StatModifier {
fun modifyStat(target: Player, statName: String, newValue: Int) {
// Your code here
}
}
fun main() {
val player = Player(10, 10)
val modifier = StatModifier()
modifier.modifyStat(player, "strength", 20)
modifier.modifyStat(player, "agility", 15)
modifier.modifyStat(player, "intelligence", 5) // Should handle as not found
println("Strength: ${player.strength}, Agility: ${player.agility}")
}There are no comments for now.