Source: pexels.com

Reflection — A Hidden JVM Superpower

Martin Macheiner
The Startup
Published in
4 min readSep 8, 2020

--

Tell me more about reflection!

Reflection is quite a powerful tool provided by the JVM. Most of the time you don’t need it, but sometimes there are problems where it comes quite handy. For me, it is always when I was confronted with the task of writing a system with plugin support. Okay, so why is reflection useful?

Reflection lets you introspect the structure of your code at runtime. It allows developers to retrieve useful information about classes, like methods, fields, or constructors. Further, it’s also possible to instantiate new objects of this class and invoke methods of such an instantiated object. This is done by asking a class for a certain method by passing in the name of the method as a string. The problem with it: There are no compiler errors, if there’s an error it happens at runtime. I know, it sounds quite abstract at the beginning.

When it comes to using reflection, I stick to this:

Do not use Reflection for code you don’t own.

There is just too much that can go wrong when you work with code that you don’t own. Internals may change without noticing or there’s a simple typo that crashes the application. And to be honest, you should never come close to the need to use reflection for any third-party code. If you ever encounter the need to do so, either make a PR to the repository if it’s open-source or search for a better alternative for your needs.

Digression: Reflection in Kotlin

Reflection in Kotlin is separately shipped from the standard library and is not available out of the box. Check out more information about reflection in Kotlin here https://kotlinlang.org/docs/reference/reflection.html.

Well, so far the theory, let’s look at some code to get a better understanding.

Example 1: Instantiation of unknown dependencies

Using reflection to instantiate dependencies is a convenient way when you don’t actually know where your dependencies are located. Sounds abstract, doesn’t it? Take this simple implementation of a plugin system as an example.

Instantiation of unknown Filter subtypes that linger in the classpath.

Reflection offers a way to grab all subtypes of a given interface (in this case Filter) that are registered in your classpath and instantiates them for you. You don’t have to explicitly name them when wiring together your dependencies. It will automatically add all the different subtypes to your program. In this case, all implementations must have a parameterless constructor, otherwise, filterClass.newInstance() will fail at runtime. (Which is one of the huge drawbacks of reflection, thus use it carefully!)

Note: The used object reflections is of type org.reflections.Reflections which comes from a quite neat library (https://github.com/ronmamo/reflections). This makes working with reflection so much easier.

Not convinced yet? Okay, let’s take a look at a more complex problem.

Example 2: Connect a generic consumer to a producer instance

Let’s say there is a data producer and a data consumer in the system. Both are connected with a pipe that transforms the data from the producer into a format that the consumer can consume.

In this example, the producer periodically emits data of the type int, while the consumer can only consume double values. The pipe will take care of the data transformation from int to double.

The Producer object holds an instance that refers to the class being instantiated while the field, which internally refers to an instance of PublishSubject<*> (from the RxJava library). The Consumer also holds an instance that refers to the consumer class being instantiated, a reference to the method that receives the data from the producer and the pipe, that is responsible for the data mapping. The method must have exactly one parameter which is of the O type of the pipe (otherwise it’ll fail at runtime).

The producer pumps data via the pipe to the consumer.

Well, how does it look in code now?

Connect a producer to a list of consumers.

The connect method takes a producer and a consumer and connects the data stream between both. Let’s start with line 14. producer.field.get(producer.instance) accesses the field value of the instance. In normal code, one would write instance.field. However, here we have the field and want the field value of that particular instance. The cast to PublishSubject<*> is obligatory because we are assuming that the field is only of this type. This is also just an assumption. This code would crash at runtime if the field is not of type PublishSubject<*>. Line 16 just takes the pipe from the consumer and transforms the emitted data into a suitable format. No magic there. Line 17 invokes the method consumer.method on the consumer.instance object, passing in transformedData as the one and only method argument.

This code connects a field from one object, that can emit data, to the method of another object, that can process the data while transforming the data if needed. Pretty cool, no?

These short examples should make two things clear:

  1. Developers can do pretty awesome things with reflection.
  2. There is so much that can go wrong with it that it should be used with caution. Actually, it should be avoided if there is another way of doing the very same task.

If you want to dig deeper into reflection, here’s the link to the Github repository of my master thesis. It contains a lot of reflection code. Just explore it by yourself. https://github.com/shockbytes/EmgFramework

--

--