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<Method> on which we can call forEach().
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 and properties.
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())
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.