Kotlin Reflection
Overview
Kotlin Reflection allows us to interact and inspect the properties, functions, and types of the objects of a class dynamically at run time. One of the useful features is that it allows us to manipulate and verify the code structure at runtime. Kotlin Reflection facilitates introspection, dynamic loading, and manipulation of classes, objects, properties, and functions.
Kotlin's Reflection API
Kotlin's Reflection lets us access properties of classes and methods of objects at runtime. Usually, the compiler ensures that all the declarations exist for the program's source code. However, sometimes the names of the methods and the properties are only known at runtime. Reflection is used in these situations.
Kotlin has two reflection APIs java.lang.reflect and kotlin.reflect packages.
Java.lang.reflect Package
The java.lang.reflect package is taken from the Java language. Kotlin classes are compiled to regular Java bytecode, thus, Java reflection API can be used in Kotlin code. Java's reflection library is fully compatible with Kotlin code.
While Java's reflection API can be used with Kotlin, Kotlin has advanced features that are absent in Java. Thus, it is sometimes uneasy to use this library in Kotlin.
Kotlin.reflect Package
The second API is Kotlin's reflection API defined in the kotlin.reflect package. It facilitates access to concepts such as nullable types which are not known to exist in Java. Kotlin's reflection API can be used to access classes written in any JVM language. It doesn't fully replace Java's reflection and there still exists cases where Java reflection is required.
Let us look at an example of Kotlin's Reflection:
Output:
Explanation: We use Kotlin's reflection API (Person::class.memberProperties) to obtain a list of properties for the Person class. We iterate over the properties, retrieve their names and values using reflection, and print them.
Accessing Class Information
The Reflection API of Kotlin allows us to access information about the class dynamically. This can help introspect all the details about the class at runtime.
The kotlin.reflect.KClass class allows us to derive class information from a Kotlin Class. The instance of the KClass can be created using the :: operator with the instance or class name.
Obtaining a KClass Instance
- Directly from a class
- From an instance of a class
- From a string
Once we have derived the instance of a KClass we can use its properties and methods to view or manipulate the classes. A few of these are discussed below:
- Check if the Class is Abstract or Final. This can be done with isAbstract or isFinal property.
- Check if the class is a Companion Class or is a Data Class
- Creating new instances with the help of class Reference or KClass object We can create a new object dynamically with the help of createInstance.
- Accessing constructions, functions, members, member properties.
Dynamic Code Analysis
Dynamic Code Analysis means inspecting and understanding code's behaviour at runtime. This can be done using Kotlin's Reflection API.
- Inspecting classes and properties An instance of a KClass can be used to explore members, constructors, properties, etc.
- Dynamic Object Creation We can use the method createInstance on the KClass object to create an object of a class.
- Invoking functions Dynamically Based on the conditions at runtime we can invoke different functions with the help of Kotlin's Reflection.
In the above code, we get the list of all the functions of a class. We filter this list to apply the desired function.
- Working with Annotations Annotations are pieces of metadata attached to code elements like classes, functions, properties, or even parameters. We can retrieve and process annotations dynamically.
- Inspecting Companion Objects It helps us move around the class hierarchy. In Kotlin we obtain this ability with the help of a Companion object for an arbitrary class and instance of an Object class.
Working with Annotations
Annotations are a special type of metadata that we can attach to classes, methods, and properties. To access these annotations in Kotlin associated with classes or objects we use Kotlin's Reflection API.
Let us begin by defining a custom annotations:
Next, we apply the annotation to a class and a function
Let us now see how we can access these annotations with the help of Reflection API at runtime.
Output:
Explanation:
- The MyAnnotation is a custom annotation defined to store messages. The MyClass class and myFunction() are annotated with MyAnnotation.
- In the main class, we get the KClass for the MyClass. The .annotations property is used to derive the annotations associated with the class.
- Firstly, we get the list of functions using the declaredFunctions property. The .find lets us filter out the KFunction instance of the desired function.
- The .annotations are used with the KFunction object to get its annotations. Next, we use .find to check if any annotations exist for this function. Lastly, we use the .call() to invoke the function dynamically.
Reflection for Serialization and Deserialization
Serialization and Deserialization libraries commonly make use of Reflection in Kotlin. Serialization is the process of transforming an object's state into a format suitable for storage or transmission.
Deserialization is the exact opposite process of serialization i.e. reconstructing an object from its serialized representation.
Reflection is used for Serialization and Deserialization when we are not aware of the desired class at compile time. Reflection provides flexibility and modularity in such scenarios.
It is also used when we want to handle data from different sources. This data can be in various data formats which can be handled dynamically.
Practical Examples
Example 1 - Inspecting Class Information
You will come across situations where you have no clue about the class. In such situations, we can use the KOtlin Reflection to get the class information at runtime.
Output:
Explanation:
- Book::class - gets an instance of KClass as a reference for the Book class.
- simpleName - provides the class name
- superclasses - returns list of parent classes
- declaredMemberProperties - gets an iterator for all the properties and is used to print the name and type of all of them.
- declaredMemeberFunctions - is the same as above but for but for the functions.
- createInstance - It is used to dynamically create an instance of a class.
Example 2 - Custom Deserialization with Field-Level Annotations
Let us see how we can use the Reflection API in Kotlin to custom deserialization with field-level annotations.
Output:
Explanation:
- @Exclude and @Rename(newName) are two annotations for fields to be excluded and fields to be renamed during serialization respectively.
- MyDto class represents data to be serialized. sensitiveData is to be excluded and internalName is to be renamed to its publicName.
- serializeWithReflection(dto) takes a MyDto instance and returns a serialized map.
- We are creating an empty map serializedData to store serialized property values.
- To get the properties dto::class.declaredMemberProperties is used.
- For each property, we get annotations.
- These annotations are used to understand what actions should be performed on data. Whether to exclude, rename, or do nothing.
- Finally, get the value and add it to the map.
- Return the map.
Conclusion
- Kotlin's Reflection is used to inspect and modify methods, properties, functions, and instances of a class at runtime.
- The instance of KClass provides a reference to a Class. This reference is further used to inspect. ::class operator is used to get the KClass.
- Kotlin has two reflection APIs java.lang.reflect derived from Java and kotlin.reflect specific to Kotlin operations.
- Common use cases of Reflection API of Kotlin are code analysis, dynamic plugin loading, custom serialization/deserialization, custom annotations to dynamically adapt behaviour, etc.