Reflection and Introspection in Kotlin: How to Look Inside Your Code
Imagine you are a programmer writing code that analyzes itself. Sounds like science fiction? In the world of Kotlin, it's a reality. Reflection and introspection are the tools that turn your code from a simple executor into a true meta-programmer. If you've ever wanted to dynamically get information about classes, call methods by name, or work with annotations at runtime — this article is for you.
We'll break down how Kotlin, as a language running on top of the JVM, uses reflection, and how with simple examples you can add magic to your projects. Let's go!
What are Reflection and Introspection? The Difference You Need to Know
Before diving into the code, let's clarify the terms. These concepts are often confused, but there is a fine line between them.
- Introspection — is the ability of a program to "look at itself." You can get information about the structure of classes: what properties, methods, and constructors they have. It's like opening the hood of a car and seeing what's inside.
- Reflection — is a more powerful thing. You not only look but also change the program's behavior during execution. You can call methods you didn't know about at compile time, create objects by the string name of a class.
In Kotlin, reflection is closely tied to JVM reflection but has its own Kotlin-specific APIs (e.g., KClass, KFunction). And yes, to work with reflection, you'll need to add a dependency in build.gradle.kts:
dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22")}Without it, you can only use basic features, like javaClass. But for the full Kotlin experience — add the library.
KClass: Your Main Introspection Tool
In Java, we are used to Class<?>. In Kotlin, its equivalent is KClass. This is the entry point into the world of reflection. You can get a KClass in two ways:
// Method 1: via the class objectval myClass: KClass<String> = String::class
// Method 2: via an instanceval str = "Hello, world!"val classFromInstance: KClass<out String> = str::classNow you have access to a wealth of information: class name, package, constructors, properties, functions. Let's look at an example:
data class User(val name: String, val age: Int)
fun main() { val kClass = User::class println("Class name: ${kClass.simpleName}") // User println("Package: ${kClass.qualifiedName}") // com.example.User println("Number of properties: ${kClass.memberProperties.size}") // 2 (name, age) kClass.memberProperties.forEach { prop -> println("Property: ${prop.name}, type: ${prop.returnType}") }}This is pure introspection in Kotlin — we are just looking at the structure. No behavior modification.
Getting Information About Functions and Constructors
Sometimes you need to know what methods a class has, or even call them. For this, we use KFunction.
class Calculator { fun add(a: Int, b: Int): Int = a + b fun multiply(a: Int, b: Int): Int = a * b}
fun main() { val calcClass = Calculator::class println("Functions of the Calculator class:") calcClass.memberFunctions.forEach { func -> println(" - ${func.name} (parameters: ${func.parameters})") }}The output will be something like this:
Functions of the Calculator class: - add (parameters: [a, b]) - multiply (parameters: [a, b])Reflection in Action: Calling Methods and Creating Objects
Now for the most interesting part — reflection. Let's say you have a string with a method name,