Kotlin

Reflection

Introduction#

Reflection is a language’s ability to inspect code at runtime instead of compile time.

Remarks#

Reflection is a mechanism to introspect language constructs (classes and functions) at the runtime.

When targeting JVM platform, runtime reflection features are distributed in separate JAR: kotlin-reflect.jar. This is done to reduce runtime size, cut unused features and make it possible to target another (like JS) platforms.

Referencing a class

To obtain a reference to a KClass object representing some class use double colons:

val c1 = String::class
val c2 = MyClass::class

Referencing a function

Functions are first-class citizens in Kotlin. You can obtain a reference on it using double colons and then pass it to another function:

fun isPositive(x: Int) = x > 0

val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]

Inter-operating with Java reflection

To obtain a Java’s Class object from Kotlin’s KClass use the .java extension property:

val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java

val c2: Class<MyClass> = MyClass::class.java

The latter example will be optimized by the compiler to not allocate an intermediate KClass instance.

Getting values of all properties of a class

Given Example class extending BaseExample class with some properties:

open class BaseExample(val baseField: String)

class Example(val field1: String, val field2: Int, baseField: String): 
    BaseExample(baseField) {
    
    val field3: String
        get() = "Property without backing field"

    val field4 by lazy { "Delegated value" }

    private val privateField: String = "Private value"
}

One can get hold of all properties of a class:

val example = Example(field1 = "abc", field2 = 1, baseField = "someText")

example::class.memberProperties.forEach { member ->
    println("${member.name} -> ${member.get(example)}")
}

Running this code will cause an exception to be thrown. Property private val privateField is declared private and calling member.get(example) on it will not succeed. One way to handle this it to filter out private properties. To do that we have to check the visibility modifier of a property’s Java getter. In case of private val the getter does not exist so we can assume private access.

The helper function and it’s usage might look like this:

fun isFieldAccessible(property: KProperty1<*, *>): Boolean {
    return property.javaGetter?.modifiers?.let { !Modifier.isPrivate(it) } ?: false
}

val example = Example(field1 = "abc", field2 = 1, baseField = "someText")

example::class.memberProperties.filter { isFieldAccessible(it) }.forEach { member ->
    println("${member.name} -> ${member.get(example)}")
}

Another approach is to make private properties accessible using reflection:

example::class.memberProperties.forEach { member ->
    member.isAccessible = true
    println("${member.name} -> ${member.get(example)}")
}

Setting values of all properties of a class

As an example we want to set all string properties of a sample class

class TestClass {
    val readOnlyProperty: String
        get() = "Read only!"

    var readWriteString = "asd"
    var readWriteInt = 23

    var readWriteBackedStringProperty: String = ""
        get() = field + '5'
        set(value) { field = value + '5' }

    var readWriteBackedIntProperty: Int = 0
        get() = field + 1
        set(value) { field = value - 1 }

    var delegatedProperty: Int by TestDelegate()

    private var privateProperty = "This should be private"

    private class TestDelegate {
        private var backingField = 3

        operator fun getValue(thisRef: Any?, prop: KProperty<*>): Int {
            return backingField
        }

        operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: Int) {
            backingField += value
        }
    }
}

Getting mutable properties builds on getting all properties, filtering mutable properties by type. We also need to check visibility, as reading private properties results in run time exception.

val instance = TestClass()
TestClass::class.memberProperties
        .filter{ prop.visibility == KVisibility.PUBLIC }
        .filterIsInstance<KMutableProperty<*>>()
        .forEach { prop ->
            System.out.println("${prop.name} -> ${prop.get(instance)")
        }

To set all String properties to "Our Value" we can additionally filter by the return type. Since Kotlin is based on Java VM, Type Erasure is in effect, and thus Properties returning generic types such as List<String> will be the same as List<Any>. Sadly reflection is not a golden bullet and there is no sensible way to avoid this, so you need to watch out in your use-cases.

val instance = TestClass()
TestClass::class.memberProperties
        .filter{ prop.visibility == KVisibility.PUBLIC }
        // We only want strings
        .filter{ it.returnType.isSubtypeOf(String::class.starProjectedType) }
        .filterIsInstance<KMutableProperty<*>>()
        .forEach { prop ->
            // Instead of printing the property we set it to some value
            prop.setter.call(instance, "Our Value")
        }

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow