1. Overview
In this tutorial, we’re going to enumerate a few ways to instantiate Kotlin objects with the help of reflection and KClass tokens.
2. Dependencies
Over the course of this article, we’re going to use the kotlin-reflect module pretty extensively. Therefore, we should add its dependency to our build file such as pom.xml:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.5.21</version>
</dependency>
Moreover, to manipulate KClass tokens, we’ll use these three classes:
class NoArg
class OptionalArgs(val arg: String = "default")
class RequiredArgs(val arg1: String, val arg2: String) {
constructor(arg1: String): this(arg1, "default")
}
The first one has a no-arg constructor, the second constructor only accepts one optional parameter, and the final one requires two mandatory parameters. In addition to these, the last class has a secondary constructor, as well.
3. Reflective Instantiation
As of Kotlin 1.1, the extension function createInstance() on KClass tokens can create object instances:
val noArgInstance = NoArg::class.createInstance()
assertNotNull(noArgInstance)
assertThat(noArgInstance).isInstanceOf(NoArg::class.java)
This extension function only works for classes with no-arg constructors or constructors with only optional parameters. Therefore, we can create an instance of the OptionalArgs class, as well:
val instance = OptionalArgs::class.createInstance()
assertNotNull(instance)
assertThat(instance).isInstanceOf(OptionalArgs::class.java)
However, if we try to instantiate a class whose constructor has required constructor arguments, it will throw an exception:
val exception = assertThrows<IllegalArgumentException> { RequiredArgs::class.createInstance() }
assertThat(exception).hasMessageStartingWith("Class should have a single no-arg constructor")
As shown above, it throws an instance of IllegalArgumentException because the primary constructor has two required parameters.
3.1. Primary Constructor
In order to create an instance of a class with at least one required constructor argument, we can call the primary constructor reflectively:
val primaryConstructor = RequiredArgs::class.primaryConstructor
val instance = primaryConstructor!!.call("first arg", "second arg")
assertNotNull(instance)
assertThat(instance).isInstanceOf(RequiredArgs::class.java)
Here, the primaryConstructor extension property finds the primary constructor. Moreover, the call() function executes the given constructor with the given arguments.
3.2. Secondary Constructor
When there are multiple constructors, we can use the constructors extension property to find the appropriate one:
val constructors = RequiredArgs::class.constructors
val instance = constructors.first { it.parameters.size == 1 }.call("arg1")
assertEquals("arg1", instance.arg1)
assertEquals("default", instance.arg2)
Here, we’re filtering to find a constructor with just one argument.
4. Conclusion
In this article, we got familiar with a couple of ways to instantiate Kotlin objects given a KClass token.
As usual, all the examples are available over on GitHub.