Understanding Reflection using Kotlin

Almost all frameworks or libraries utilize the power of reflection under the hood. Knowing about it would make you a better developer.

Nilanjan 🌱🌱
Level Up Coding

--

Have you ever wondered how the libraries or frameworks, that rely on the JVM-based languages such as Kotlin or Java, work under the hood? To me, it had been some sort of black magic unless I came across the idea of reflection. In libraries like Retrofit or Kotlin-serialization, you define a bunch of classes or interfaces and annotate them with some predefined keywords and it works perfectly in the runtime. The same is true for Android. You extend some classes such as Main-Activity or ViewModel and the end result is a functioning app.

However, it is really not obvious that how do these libraries know what you defined or exactly where you did that. There must be some mechanism built into these languages that allow playing with the statically defined classes and interfaces at runtime. This is precisely what reflection is built for.

In this article, I would attempt to demystify reflection through a few trivial code examples. I understand that you are not always supposed to dig into the details of a library. It is crucial for a developer to be able to appreciate abstraction. Although, getting a high-level glimpse of how these frameworks function under the hood would, of course, augment your development toolbox.

Who knows you might be building a bleeding-edge framework, that solves many problems of those that are in existence right now, in the upcoming years or so.

Disclaimer: Keep in your mind that most frameworks or libraries will not always be using reflection for everything. Since reflection is a slower process as compared to the other primitive programming stuff the frameworks would implement intermediate compilers.

What is reflection?

In a nutshell, reflection enables you to reference statically defined classes, functions, or interfaces at runtime. Post that you can introspect it. You may,

  1. Access the properties or functions of the class irrespective of the access-modifier
  2. Find out whether any member of that class has been annotated with some particular annotations
  3. Manipulate the class/interface as necessary

That is an immense power. As I have mentioned you can even manipulate the private properties of a class. So, yeah, you have to be scrupulous while using reflection as it breaks encapsulation.

With great power comes great responsibility

Dependency

Before using reflection in your Kotlin project you have to add the following dependency to your build.gradle file.

Reflection in action

We will begin by defining a dummy class that would let you taste reflection.

We can, now, instantiate the class itself either from the class name or from the object of that class.

As shown in the code snippet, there is two way to grab the reference of the class. We can either user className::class or objectReference::class.

To obtain the Java equivalent class you may use the className/objectReference::class.java syntax.

While these are almost the same, there are subtle differences between the types that these varying syntaxes would produce.

Why the designers of the language have decided to use two different types, i.e KClass<T> and KClass<out T> or Class<T> and Class<out T>, is beyond the scope of this article.

If you are familiar with Kotlin’s way of expressing type-variance in generics you may have already understood that the interfaces which have <out T> are actually implemented with declaration-site variance. If you have no idea what type-variance actually is you may later check out this enlightening illustrated guide on Kotlin’s type variance. However, you need not worry much about type-variance right now. You don’t need to learn it to understand reflection.

Accessing the member functions

It is time that we do something with the class reference. Let’s start with something simple — print the name of all the member functions of that class.

The memberFunctions property returns a list of all the member functions of a given class. If you are wondering where do the methods, that we haven’t explicitly declared, namely equals hashCode and toString, came from here is the explanation. These three methods are declared in Kotlin’s Any class which all other classes — by default — inherit from unless stated otherwise.

Invoking the member functions

This is how we can invoke the member functions using the KClass<out Dummy> variable.

The only thing to notice here is whenever we want to invoke a function using a reference that is of KFunction<*> type we need to pass an object that is an instantiation of the corresponding class while using the call function. The reason is straightforward, a class is just a blueprint it cannot behave independently. Hence, an object is needed.

Invoking a private function

Up until now, we have only worked with public functions from that class. However, as I have mentioned that private functions and property can also be accessed here is an example of it.

To access a private function there is an extra step. We need to explicitly set the isAccessible property of that KFunction<*> variable as true. Failing to do that would inevitably welcome an IllegalAccessException at runtime.

Accessing properties (private)

Here is how you can access the properties of the class.

As you can see the process is really similar to the previous demonstrations. Just like the private functions, it is necessary to explicitly make private properties accessible. The type of the properties, in this case, i.e KProperty1<out Dummy, *> , is a bit cumbersome. Although, you need not worry about it too much if you could not grasp it right away. The KProperty type has a couple of variants designed for different properties. You may learn more in the Kotlin API reference for KProperty.

Manipulating property values

To manipulate the values of the properties we need to cast the property into the KMutableProperty type.

In the shown code-snippet we have modified the value of the property before the getter actually accessed it. Thus the output is different.

A Brief Summary

  1. Reflection enables us to introspect or manipulate the behavior of a class or interface at runtime.
  2. Using reflection we can even access the properties and functions marked with the private access-modifier.
  3. Reflection is heavily utilized in a library or framework.
  4. It can be found whether any class or its members were marked with some predefined annotations so that a library or framework can act accordingly.
  5. Reflection is not a very fast process. Therefore, heavy frameworks or libraries seldom implement intermediate compilers.
  6. Reflection gives us enormous power. So, utmost care must be taken while using it.

Wrapping-Up

We will be wrapping up here for today. Hopefully, you have gained a basic level of understanding of reflection. You also have a general idea about what goes on under the hood while our code uses a framework or library.

Although, well will not simply end our quest here. In the next article, we will be building a simple annotation processor. We would define our very own annotations. The end result will be a dummy string parser that would encode/decode JSON data. Annotation and reflection go hand in hand. So, I would urge you to stay tuned for the next one.

Thank you so much for making it thus far. I would really appreciate a couple of words on your thought ✏️.

You can support my work by buying me a cup of coffee. ☕

--

--