Photo by Yeshi Kangrang on Unsplash

Reflection is a powerful framework that gives us the tools to inspect the structure of both classes and objects available at runtime. Like Java and many other languages that run on the JVM, Kotlin supports reflection. But… is it very different from Java?

APIs available

Kotlin uses 2 different reflection libraries:

  • the good-old-buddy from the Java world “java.lang.reflection” package.
  • the new-and-shiny “kotlin-reflect” package, available here. Unlike the Java package, this one has to be added explicitly to our project if we want to use kotlin reflection.

Having these 2 components at the same time means that Kotlin offers 100% compatibility with the Java reflection mechanism, but also provides a new library in order to manage some specific features, such as the nullability system, for instance.

The Kotlin reflect API

It contains a hierarchy of classes and interfaces that represent each one of the components declared in our source code: classes, subclasses, constructors, methods, properties, function parameters, annotations and so on.

The main components are shown in the following image:

Kotlin reflect API

Most of these classes use generics, so they receive one or multiple type arguments. For instance:

//XXX: object representing a class, more precisely the "String" class
val clazz1 : KClass<String> = ...

//XXX: another object representing another class, in this case the "Number" class 
val clazz2 : KClass<Number> = ...

When working with reflection, the source code may seem more complicated, but keep in mind that the principles of O.O.P. still apply. Under the hood, we will be just creating instances of classes and using some of its properties or methods to “inspect” our code.

The following sections will explore the API in more detail. On almost every example we will apply reflection over the following custom data class:

data class Person(val personName : String, val personAge : Int)

KClass<>

Instances of KClass represent a kotlin class. Its type parameter defines the specific class represented (String, Number, Person, or any other). Each one of the classes used as type arguments have the same elements (constructors, methods, attributes, etc) but obviously their contents differ.

KClass instances can be obtained:

  • using an object, through the javaClass.kotlin property.
  • using a specific class name, accessing its ::class attribute.
val p = Person("John", 20)

//XXX: access using object
val pClazz1 : KClass<Person> = p.javaClass.kotlin

//XXX: access using class
val pClazz2 : KClass<Person> = Person::class

println(pClazz1.name)
println(pClazz2.name)

Most of the times, KClass acts as an entry point to the reflection world, because through it we can retrieve:

  • the inspected class name
  • its constructors
  • its properties
  • its methods
  • its subclasses
  • and so on…

The following snippet, for instance, retrieves the constructors of our Person class:

val clazz : KClass<Person> = Person::class
val construcs = clazz.constructors
    
construcs.forEach {    
    println(it)    
}

KCallable<>

The KCallable interface represents a method or an attribute of a class. Its type parameter specifies:

  • the type of a given property (when applied on attributes)
  • a function return type (when applied over methods)

KCallable declares the call() method, allowing us to invoke a function or an accessor (when applied on attributes).

val p = Person("John", 20)

//XXX: ref to age (Int) property 
var pCall : KCallable<Int> = p::personAge
    
//XXX: call() invokes accessor
println("${p.personName}'s age is ${pCall.call()}")

However, keep in mind that call() is not a type safe method. It can be invoked with any number of parameters and types, but we will get runtime errors if the expected values and the received ones do not match.

KFunction<>

KFunction inherits from KCallable and is another abstraction used to represent only methods. Its type parameter specifies the data type returned by the function invocation.

val p = Person("John", 20)

val mFun : KFunction<Boolean> = p::isAdult

val result = mFun.call(21)

println("Result is $result of type ${result.javaClass}")

Class constructors are functions too, so they’re also represented using this interface.

Among other properties, KFunction contains several handy flags that match each one of the function modifiers available when declaring a function:

  • isSuspend (suspend modifier)
  • isInline (inline modifier)
  • isOpen (open modifier)

KProperty<>

KProperty also inherits from KCallable, but it represents an attribute instead of a function. We can also use it to inspect local/global variables and constants.

In most of the cases, when applying reflection on these elements, we will use KProperty subclasses instead of the superclass itself. To simplify things, we could say that this hierarchy contains subclasses depending on:

  • a property mutability: is the element inspected declared as mutable (var) or immutable (val)?
  • a property scope: is the element a class property, a local variable or a global one…? (NOTE: this is not 100% accurate, because in fact the library checks for class receivers instead of scopes).

So, taking into account both aspects, we have available subclasses such as:

  • KProperty0<>, applied on constants.
  • KMutableProperty0<>, applied on variables.
  • KProperty1<>, used with immutable class attributes.
  • KMutableProperty1<>, used with read-write class properties.

K(Mutable)Property0<T> takes a single argument (T) representing the type of the variable/constant inspected and declares the corresponding get()/set() methods:

//XXX: some global constant of type Int...
const val THRESHOLD = 18

//XXX: ... can be inspected with
val mGlobalProp : KProperty0<Int> = ::THRESHOLD

println("global var has value ${mGlobalProp.get()}")

On the other hand, K(Mutable)Property1<C, P> takes 2 type arguments, one for the type class containing the property inspected (C) and another one for the property itself (P):

val p1 = Person("John", 40)
    
val mProp : KProperty1<Person, Int> = Person::personAge
    
println("${p1.personName}'s age is ${mProp.get(p1)}")

The property hierarchy also contains the KProperty2<> interface, available for extension attributes.

KAnnotatedElement

This interface acts as the superclass for all elements in the kotlin-reflect API, because annotations can be applied on any component (a class, a function, a property, even a variable can be annotated in Kotlin).

KAnnotatedElement is not a generic interface: it only defines a property called “annotations” consisting on a list of annotations available at runtime. This property is inherited by all subclasses, so every instance of KClass, KProperty, KFunction… has its corresponding annotation list.

In order to easily traverse these elements, the class also declares the “findAnnotation()” method, that looks for some annotation of a given type and returns it when it’s present (or null if it is not):

val mProp : KProperty1<> = ...
//XXX: expect possible null values cause annotation may not be declared
val mCustomAnnotation : MyCustomAnnotation? = mProp.findAnnotation<MyCustomAnnotation>()
     
mCustomAnnotation?.let {
   println(it.annotationClass )
   println(it.value)
}

Wrapping up

Kotlin is fully compatible with Java reflection and gives us the ability to dinamically manipulate unkown elements: classes, functions, methods, annotations… all of them have their corresponding element in the kotlin-reflect API and can be accessed at runtime.

As usual, check this sample code to give reflection a try.

Write you next time!

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: