Skip to Content
Course content

107: Reflecting Over Properties and Functions

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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: Use call() and remember to include the instance as the first argument for member functions.
  • KProperty: Use get() (for KProperty1) 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 Player class has var strength: Int and var agility: Int.
  • You must use Player::class.members to find the property matching statName.
  • You must cast the member to a KMutableProperty1<Player, Int> to use the setter.
  • Handle the case where the statName provided doesn't exist in the Player class (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}")
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.