Introducing Kotlin reflection
Reflection is like marmite, most people hate it. However scary or ugly reflection may seem, learning about Kotlin features will give you a better understanding of what is possible with the language. So, this is an introduction to reflection as well as when it might be useful to use.
What is reflection?
When reflecting on previous events within your life, you are able to think about the “things” that built up to that moment. You can then make changes in relation to these reflections of past events.
In software development, reflection is very similar. At runtime, you are able to examine and “introspect” the application and its parts. You are then able to mutate and act on those introspections.
Reflection with Kotlin
To ensure that you can use reflection in your project, make sure to include the kotlin-reflect
dependency in your project.
Class references
One of the simpler parts of the reflection library and something you may have already used in your own Koltin projects, class references and bound class references provide you with introspection capabilities on a class.
1 |
|
Callable references
The Kotlin docs has six sections about callable references. They are as follows:
- Function references
- Property references
- Interoperability with Java reflection
- Constructor references
- Bound function and property references
- Bound constructor references
I won’t go through each of the types of callable reference here. Instead, I will give you a brief introduction and then move on to possible uses for reflection.
Kotlin has been built in such a way to allow for Object Oriented or Functional Programming styles to be used. Reflection is an important part of the functional programming style.
Where a function call would usually look something like isOdd(5)
, Kotlin reflection allows us to pass references to functions by using the ::
operator.
1 |
|
Where you see the ::
operator, you can assume that it has something to do with Kotlin reflection.
Building an object using reflection
Sometimes, you may find it useful to build an object, given some class.
This approach may become useful when generics and polymorphism prove too tricky to achieve desired results.
You can use property reference or constructor reference to build your objects. In the property reference example, you need to have access to the class at compile time. In the event you have access to the class at compile time, there is unlikely a reason you should be using reflection. In the second example, we use constructor reference. This is of course not type safe and changes to the class will break silently due to the lack of compiler.
1 |
|
Using introspection, we can access the list of properties found within some class, via class reference, and can then set each of the values in turn.
1 |
|
It is always worth taking some time to assess your situation and trying multiple approaches to avoid building objects via reflection. Over time, reflection will become very difficult to manage.
Calling functions on a class
You may wish to call functions on an object/class without knowing the functions available at compile time.
Kotlin reflection allows us to introspect classes, extract the methods available on a class and then call these methods.
1 |
|
As you can see, we are able to run all the functions on the class SomeClass, including private functions, all from a class reference. To call a private function, you need to set the isAccessible
flag to true.
Why and when to use reflection
If you talk to any battle hardened developer, the answer to “When should I use reflection?” will be, to never use reflection and to instead, kill it with fire.
You may be building a framework in which user defined classes are operated on. Your framework will not know about the constituent parts of the user’s data model at compile time should your framework be compiled and then provided to your users. Introspection can be used to extract this information at runtime. Many frameworks use reflection however the amount of reflection being used is limited to the absolute minimum necessary.
Reflection is slower than calling methods directly but gives us huge power to do almost anything within our application. Dangerously, these powers let us subvert the guarantees that our programming languages provide us. There is no compiler to come to the rescue when you do something wrong or bad.
Conclusion
It is rude to go behind somebody’s back. So why would you go behind the back of your compiler?
Reflection is an important language feature but it should only be used as a last resort.
Frameworks such as Spring are heavily reliant on Java reflection APIs. Quarkus and Micronaut use ahead-of-time compilation and code generation to remove as much reflection as possible. Even Spring Framework is moving towards ahead-of-time compiled native images using GraalVM (Spring Native).
Does this mean that reflection is pointless and dead? No, not quite. We can see that reflection can be used to build great products and the pitfalls can be addressed via strategies such as ahead-of-time compilation and code generation.