1. Introduction
Reflection is the name for the ability to inspect, load, and interact with classes, fields, and methods at runtime. We can do this even when we don’t know what they are at compile time.
This has a large number of uses, depending on what we are developing. For example, frameworks like Spring make heavy use of it.
Support for this is built into the JVM and thus is implicitly available for all JVM-based languages. However, some JVM languages have extra support on top of what is already available.
2. Java Reflection
All the standard Java Reflection constructs are available and work perfectly well with our Kotlin code. This includes the java.lang.Class class as well as everything in the java.lang.reflect package.
If we want to use the standard Java Reflection APIs for any reason, we can do so in exactly the same way that we would in Java. For example, to get a list of all the public methods in a Kotlin class we would do:
MyClass::class.java.methods
This breaks down into the following constructs:
- MyClass::class gives us the Kotlin Class representation for the MyClass class
- .java gives us the java.lang.Class equivalent
- .methods is a call to the java.lang.Class.getMethods() accessor method
This will work exactly the same whether called from Java or Kotlin, and whether called on a Java or a Kotlin class. This includes Kotlin specific constructs, such as Data Classes.
data class ExampleDataClass(
val name: String, var enabled: Boolean)
ExampleDataClass::class.java.methods.forEach(::println)
Kotlin converts the returned types to the Kotlin representations as well.
In the above, we get a kotlin.Array
3. Kotlin Reflection Enhancements
Whilst we can use the standard Java Reflection APIs, it isn’t aware of all extensions that Kotlin brings to the platform.
Additionally, it can on occasion be a bit awkward to use in some situations. Kotlin brings its own reflection API that we can use that solves these problems.
All entry points into the Kotlin Reflection API use References. Earlier on, we saw the use of ::class to give a reference to the Class definition. We’ll also be able to use this to get references to methods, properties, and annotations.
3.1. Kotlin Class References
The Kotlin Reflection API allows access to a Class reference. This can then be used to introspect the full details of the Kotlin class. This gives access to the Java Class reference – the java.lang.Class object – but also to all the Kotlin specific details.
The Kotlin API for class details centers around the kotlin.reflect.KClass class. This can be accessed by using the:: operator from any class name or instance** – e.g. String::class.
Alternatively, it can be accessed by using the extension method java.lang.Class.kotlin if a Java Class instance is available to us:
val listClass: KClass<List> = List::class
val name = "Baeldung"
val stringClass: KClass<String> = name::class
val someClass: Class<MyType>
val kotlinClass: KClass<MyType> = someClass.kotlin
Once we have obtained a KClass object, there are some simple things that it can tell us about the class in question. Some of these are standard Java concepts, and others are Kotlin specific concepts.
For example, we can easily find out if a Class is Abstract or Final, but we can also find out if the Class is a Data Class or a Companion Class:
val stringClass = String::class
assertEquals("kotlin.String", stringClass.qualifiedName)
assertFalse(stringClass.isData)
assertFalse(stringClass.isCompanion)
assertFalse(stringClass.isAbstract)
assertTrue(stringClass.isFinal)
assertFalse(stringClass.isSealed)
We also have ways to move around the class hierarchy. In Java, we can already move from a Class to its superclass, interfaces and the outer class it’s enclosed in – if appropriate.
Kotlin adds to this the ability to obtain the Companion Object for an arbitrary class, and the Object instance for an Object class:
println(TestWithCompanion::class.companionObject)
println(TestWithCompanion::class.companionObjectInstance)
println(TestObject::class.objectInstance)
We can create new instances of a class from a Class Reference as well, in much the same way as in Java:
val listClass = ArrayList::class
val list = listClass.createInstance()
assertTrue(list is ArrayList)
Alternatively, we can access the constructors and use an explicit one if we need to. These are all Method references as discussed in the next section.
In a very similar way, we can get access to all of the methods, properties, extensions, and other members of the class:
val bigDecimalClass = BigDecimal::class
println(bigDecimalClass.constructors)
println(bigDecimalClass.functions)
println(bigDecimalClass.memberProperties)
println(bigDecimalClass.memberExtensionFunctions)
3.2. Kotlin Method References
In addition to being able to interact with Classes, we can also interact with Methods and Properties.
This includes class properties – defined with val or var, standard class methods, and top-level functions. As before, this works equally well on code written in standard Java as it does on code written in Kotlin.
In exactly the same way as with classes, we can obtain a reference to a Method or Property using the*::* operator.
This looks exactly the same as in Java 8 to obtain a method reference, and we can use it in exactly the same way. However, in Kotlin this method reference can also be used to get reflection information about the target.
Once we have obtained a method reference, we can call it as if was really the method in question. This is known as being a Callable Reference:
val str = "Hello"
val lengthMethod = str::length
assertEquals(5, lengthMethod())
We can also get more details about the method itself, in the same way, that we can for classes. This includes both standard Java details as well as Kotlin specific details such as if the method is an operator or if it’s inline:
val byteInputStream = String::byteInputStream
assertEquals("byteInputStream", byteInputStream.name)
assertFalse(byteInputStream.isSuspend)
assertFalse(byteInputStream.isExternal)
assertTrue(byteInputStream.isInline)
assertFalse(byteInputStream.isOperator)
In addition to this, we can get more information about the inputs and outputs of the method through this reference.
This includes details about the return type and the parameters, including Kotlin specific details – such as nullability and optionality.
val str = "Hello"
val method = str::byteInputStream
assertEquals(
ByteArrayInputStream::class.starProjectedType,
method.returnType)
assertFalse(method.returnType.isMarkedNullable)
assertEquals(1, method.parameters.size)
assertTrue(method.parameters[0].isOptional)
assertFalse(method.parameters[0].isVararg)
assertEquals(
Charset::class.starProjectedType,
method.parameters[0].type)
3.3. Kotlin Property References
This works exactly the same for Properties as well, though obviously, the details that can be obtained are different. Properties instead can inform us if they are constants, late initialized, or mutable:
lateinit var mutableProperty: String
val mProperty = this::mutableProperty
assertEquals("mutableProperty", mProperty.name)
assertTrue(mProperty.isLateinit)
assertFalse(mProperty.isConst)
assertTrue(mProperty is KMutableProperty<*>)
Note that the concept of Properties also works in any non-Kotlin code. These are identified by fields that follow the JavaBeans conventions regarding getter and setter methods.
This includes classes in the Java standard library. For example, the Throwable class has a Property Throwable.message by virtue of the fact that there is a method getMessage() defined in it.
We can access the actual Property through Method references that are exposed – the getter and setter methods. The setter is only available if we are working with a KMutableProperty – i.e. the property was declared as var, whereas the getter is always available.
These are exposed in an easier to use way via the get() and set() methods. The getter and setter values are actual method references, allowing us to work with them exactly the same as any other method reference:
val prop = this::mutableProperty
assertEquals(
String::class.starProjectedType,
prop.getter.returnType)
prop.set("Hello")
assertEquals("Hello", prop.get())
prop.setter("World")
assertEquals("World", prop.getter())
3.4. Kotlin Annotation Reference
We’ve learned how to reference different elements, such as classes, methods, and properties, using Kotlin reflection. Annotations are special types of metadata that we can attach to those elements.
We can access annotations using Kotlin Reflection API as well. Further, we can use the same method to access annotations on different kinds of elements. Therefore, in this section, we’ll take annotations on a class as an example to show how to access them using Kotlin Reflection API.
First, let’s create a couple of annotations and a class that is annotated by the two annotations:
annotation class BaeldungExample(val url: String)
annotation class NotTested(val reason: String)
@NotTested("We trust developers ^_*")
@BaeldungExample("https://www.baeldung.com")
class BaeldungClass
As the code above shows, we’ve defined two annotations, and both annotations have a property. Also, we’ve attached the two annotations on BaeldungClass.
Once we have the reference of the target element, for example, a class, we can reference annotations on the given class by the findAnnotation() function:
val theClass = BaeldungClass::class
val notTestedAnnotation = theClass.findAnnotation<NotTested>()
assertNotNull(notTestedAnnotation)
assertEquals("We trust developers ^_*", notTestedAnnotation!!.reason)
The findAnnotation() function takes a KClass object and returns the first instance of an annotation that matches the specified type or null if no annotation is found.
As we can see, accessing annotations using the findAnnotation() function is pretty convenient.
Additionally, Kotlin provides the annotations property, which returns a list of all the annotations of the given type:
val theClass = BaeldungClass::class
val annotations = theClass.annotations
assertEquals(2, annotations.size)
Of course, we can get the expected annotation from the annotations property and perform further work. Next, let’s get the @BaeldungExample annotation on the BaeldungClass class using this approach:
val theClass = BaeldungClass::class
val baeldungAnnotation = theClass.annotations.filterIsInstance<BaeldungExample>().first()
assertNotNull(baeldungAnnotation)
assertEquals("https://www.baeldung.com", baeldungAnnotation.url)
It’s worth mentioning that we’ve used the filterIsInstance() function to get the @BaeldungExample annotation. The filterIsInstance() function filters a list of elements based on their type. This function is pretty handy for retrieving elements of a certain type from a list of mixed types.
4. Summary
This article gives an overview of some of the things that can be achieved with reflection in Kotlin, including both how it interacts with and differs from the reflection capabilities built into the standard Java language.
All of the examples are available over on GitHub.