kotlinkotlin-reflect

Hot to get the parameterless constructor through Kotlin Reflection API?


Given a domain class with a parameterless constructor, how do we get a reference to that constructor through the Reflection API?

Consider for example a Student data class, such as:

data class Student(var nr: Int = 0, var name: String? = null)

Notice, we can confirm the presence of the parameterless constructor through javap that shows:

public pt.isel.Student(int, java.lang.String);
    descriptor: (ILjava/lang/String;)V

public pt.isel.Student(int, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker);
    descriptor: (ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V

public pt.isel.Student();
    descriptor: ()V

Yet, none of the following approaches returns the parameterless constructor:

val constructor = Student::class.primaryConstructor
// java.util.NoSuchElementException: Collection contains no element matching the predicate
val constructor = Student::class.constructors.first { it.parameters.isEmpty() }

Alternatively, we can proceed via Java Reflection that works fine, but it should not be necessary such detour:

val constructor = Student::class.java.getDeclaredConstructor()

Second, why do we need that? Because we want to instantiate a domain class at runtime. Yes, we know that createInstance() of KClass do that job. But it throws IllegalArgumentException: Class should have a single no-arg constructor if there is no parameterless constructor.

Thus, we would like to check before-hand if we could call the createInstance() with no exceptions.


Solution

  • The parameterless constructor here only exists in the compiled Java class, and not in your Kotlin code. As far as Kotlin code is concerned, your Student class has one single constructor, with 2 optional parameters.

    The Kotlin reflection API is designed to be platform-independent, so you have to use Java reflection to get the parameter constructor.

    If you just want to see if you can call createInstance safely, you can just check if the class has a single constructor whose parameters are all optional. This is documented:

    Creates a new instance of the class, calling a constructor which either has no parameters or all parameters of which are optional. If there are no or many such constructors, an exception is thrown.

    val isSafe = someClass.constructors.singleOrNull { 
        it.parameters.all(KParameter::isOptional) 
     } != null
    

    This is similar to how createInstance is implemented to throw the exception.