2

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:

  • Approach 1 - primaryConstructor:
val constructor = Student::class.primaryConstructor
  • Approach 2 - All constructors declared in the class:
// 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.

Miguel Gamboa
  • 8,855
  • 7
  • 47
  • 94
  • "but it should not be necessary such detour" Why not? You only see the parameterless constructor if you use `javap`, which is a Java thing. That's unrelated to the Kotlin reflection API. – Sweeper Feb 22 '22 at 11:49
  • @Sweeper `createInstance()` is part of Kotlin Reflectionm API and it may claim `IllegalArgumentException: Class should have a single no-arg constructor`. All this in Kotlin! IMO, it should also provide a way in Kotlin to let me know if I can make a call to `createInstance()` without exceptions. – Miguel Gamboa Feb 22 '22 at 11:53
  • Are you confused why `createInstance` doesn't throw an exception for `Student`? Have you read its [documentation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.full/create-instance.html)? It also checks if the parameters are optional, not just whether the class has a parameterless constructor. – Sweeper Feb 22 '22 at 12:00
  • @Sweeper I am not confused about that. As I am clarifying at the end of OP: "_we would like to check before-hand if we could call the createInstance() with no exceptions._". I do not want to use a try/catch to check if the `createInstance()` works fine, or not, and if we have to choose a different way of instantiating dynamically. – Miguel Gamboa Feb 22 '22 at 12:04

1 Answers1

3

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313