Kotlin Reflection has poor documentation so I decided to gather together all code samples that I used when I wrote my library. I hope this article will help you to save time.
This article contains a collection of recipes for Kotlin Reflection. You can find and play with all samples from the article in my Github repository.
Original reflection types
Read these articles before proceeding:
KClass and a KType are two originals classes where Kotlin reflection begins.
KClass represents a class. It contains information about a class name, constructors, members, and so on.
KType represents a type. It contains a KClass and a type argument for generics types.
Recipe 1: How to get a KClass
Method 1: From a type
val kClass: KClass<String> = String::class
Method 2: From an instance
val str = "my test"
val kClass: KClass<String> = str::class
Method 3: From a KType
val kType: KType = typeOf<String>()
val kClass: KClass<String> = kType.classifier as KClass<String>
Recipe 2: How to get a KType
Method 1: From a KClass
Simple type
val kClass: KClass<String> = String::class
val kType: KType = kClass.createType()
println(kType) // kotlin.String
Generic type with a type argument
// type for string
val kClassString: KClass<String> = String::class
val kTypeString: KType = kClassString.createType()
// type for list with strings
val kClass: KClass<List<*>> = List::class
val kType: KType = kClass.createType(listOf(KTypeProjection(KVariance.INVARIANT, kTypeString)))
println(kType) // kotlin.collections.List<kotlin.String>
Generic type with the star type
Unlike the previous example, here we lose all information about a type argument.
val kClass: KClass<List<*>> = List::class
val kType: KType = kClass.starProjectedType
assertEquals(KTypeProjection(null, null), kType.arguments.first())
println(kType) // kotlin.collections.List<*>
Method 2: From the typeOf<> method
The typeOf<>() method is experimental now. It will probably be stable in Kotlin 1.6.
Simple type
val kType = typeOf<String>()
println(kType) // kotlin.String
Generics type with a type argument
The typeOf<>() method supports generic types and returns correct type parameters information.
val kType = typeOf<List<String>>()
println(kType) // kotlin.collections.List<kotlin.String>
Method 3: From a constructor parameter
data class MyData(val list: List<String>)
val clazz = MyData::class
val constructor = clazz.primaryConstructor!!
val kType: KType = constructor.parameters.first().type
println(kType) // kotlin.collections.List<kotlin.String>
Method 4: From a member property
data class MyData(val list: List<String>)
val clazz = MyData::class
val parameters = clazz.memberProperties
val kType: KType = parameters.first().returnType
println(kType) // kotlin.collections.List<kotlin.String>
There are other ways to get a KType I gave you only several examples to show the basic principles.
Recipe 4: How to get a type argument from a generic type
A KClass doesn’t contain information about a type argument only a KType contains. So before getting the type argument you have to have the KType for your type.
val kType = typeOf<Map<String, Int>>()
val arguments = kType.arguments
val keyArgument = arguments.first().type
val valueArgument = arguments.last().type
println("the key type argument: $keyArgument") // the key type argument: kotlin.String
println("the value type argument: $valueArgument") // the value type argument: kotlin.Int
Recipe 5: Refined type parameters
Read these articles before proceeding:
- Official documentation about inline functions and refined type parameters.
- StackOverflow question about refined type parameters
Pass a KClass as an argument
fun printType(clazz: KClass<*>) {
println(clazz.qualifiedName)
}
printType(String::class) // kotlin.String
Get a KClass from a refined parameter
inline fun <reified T>printType() {
println(T::class.qualifiedName)
}
printType<String>() // kotlin.String
Get a KClass from an argument
inline fun <reified T>printType(obj: T) {
println(T::class.qualifiedName)
}
printType("test") // kotlin.String
Recipe 6: How to traverse a class
A class for examination:
annotation class FirstTestAnnotation
annotation class SecondTestAnnotation
@FirstTestAnnotation
@SecondTestAnnotation
class MyClass(
@field:FirstTestAnnotation val int: Int,
@field:SecondTestAnnotation val string: String
) {
data class InternalClassDouble(val double: Double)
data class InternalClassString(val string: String)
fun method1ReturnUnit() {
}
fun method2ReturnsString(): String = string
}
Properties traverse
val data = MyClass(123, "test")
val clazz = data::class
val properties = clazz.memberProperties
properties.forEach { println("name: ${it.name}, value: ${it.getter.call(data)}") }
The code will print:
name: int, value: 123
name: string, value: test
Declared Methods traverse
val data = MyClass(123, "test")
val clazz = data::class
val methods = clazz.declaredMemberFunctions
methods.forEach { println("name: ${it.name}, returns: ${it.call(data)}") }
The code will print:
name: method1ReturnUnit, returns: kotlin.Unit
name: method2ReturnsString, returns: test
All Methods traverse
val data = MyClass(123, "test")
val clazz = data::class
val methods = clazz.memberFunctions
methods.forEach { println("name: ${it.name}") }
The code will print:
name: method1ReturnUnit
name: method2ReturnsString
name: equals
name: hashCode
name: toString
A method call
val data1 = MyClass(123, "test")
val data2 = MyClass(321, "tset")
val clazz = MyClass::class
val equalsMethod = clazz.memberFunctions.find { it.name == "equals" }!!
val result = equalsMethod.call(data1, data2)
println("data1 equals data2: $result")
The code will print:
data1 equals data2: false
Nested classes traverse
val data = MyClass(123, "test")
val clazz = data::class
val nestedClasses = clazz.nestedClasses
nestedClasses.forEach { println("${it.qualifiedName}") }
The code will print:
MyClass.InternalClassDouble
MyClass.InternalClassString
Class annotations traverse
val clazz = MyClass::class
clazz.annotations.forEach { println("${it.annotationClass}") }
The code will print:
class FirstTestAnnotation
class SecondTestAnnotation
Field annotations traverse
Read these articles before proceeding:
val clazz = MyClass::class
val properties = clazz.memberProperties
properties.forEach { property ->
println("property name: ${property.name}")
property.javaField?.annotations?.forEach { println("annotation class: ${it.annotationClass}") }
}
The code will print:
property name: int
annotation class: class FirstTestAnnotation
property name: string
annotation class: class SecondTestAnnotation
Recipe 7: How to create a class
Using a class constructor
Array with parameters
val paramsData = arrayOf(1234, "test", Instant.now())
val clazz = SimpleClass::class
val constructor = clazz.primaryConstructor!!
val dataClass: SimpleClass = constructor.call(*paramsData)
println(dataClass) // SimpleClass(int=1234, string=test, instant=2021-10-01T15:00:00Z)
Named parameters
val paramsData = mapOf("int" to 1234, "string" to "test", "instant" to Instant.now())
val clazz = SimpleClass::class
val constructor = clazz.primaryConstructor!!
val args = constructor.parameters.associateWith { paramsData[it.name] }
val dataClass: SimpleClass = constructor.callBy(args)
println(dataClass) // SimpleClass(int=1234, string=test, instant=2021-10-01T15:00:00Z)
Using a class name
Kotlin has no method to create a KClass by name, so you have to use the Class.forName method from Java then convert obtained Class to the KClass.
To be consistent, you have to use the class.java.name method from Java to get the class name instead of the class.qualifiedName method from Kotlin, because they return different names.
val expectedClass = Adt.AdtOne(1.11, "test")
val javaName = expectedClass::class.java.name
println("kotlin name: ${expectedClass::class.qualifiedName}")
println("java name: $javaName")
val clazz = Class.forName(javaName).kotlin
val constructor = clazz.primaryConstructor!!
val actualClass = constructor.call(expectedClass.double, expectedClass.string)
println(actualClass)
assertEquals(Adt.AdtOne::class, actualClass::class)
assertEquals(expectedClass, actualClass)
The code will print:
kotlin name: Adt.AdtOne
java name: Adt$AdtOne
AdtOne(double=1.11, string=test)
As you can see from the output Kotlin’s class.qualifiedName returns a different name than Java’s class.java.name.