Good Morning Kotlin gurus.
I have an inheritance structure in which the abstract superclass implements some shared of data checks. The compiler does not complain, but upon execution an IllegalArgumentException is thrown by the JVM
fun main(args: Array<String>) {
val foo = Child("NOT_BLANK")
}
abstract class Parent(
open val name: String = "NOT_BLANK"
) {
init {
require(name.isNotBlank()) { "Firstname must not be blank" }
}
}
data class Child(
override val name: String = "NOT_BLANK"
) : Parent(
name = name
)
Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.text.StringsKt__StringsJVMKt.isBlank, parameter $receiver
at kotlin.text.StringsKt__StringsJVMKt.isBlank(StringsJVM.kt)
at com.systemkern.Parent.<init>(DataClassInheritance.kt:24)
at com.systemkern.Child.<init>(DataClassInheritance.kt:30)
at com.systemkern.DataClassInheritanceKt.main(DataClassInheritance.kt:17)
Thanks for your time
all the best
On name.isNotBlank()
, you should be getting a lint warning like this:
Accessing non-final property name in constructor
You're accessing the name
property at the time when the Parent
is being constructed, however, name
is overridden in Child
. This override means that internally, both the Parent
and Child
classes have private fields for name
, and since the Parent
constructor is the first thing called when a Child
is created, the Child
's name
won't be initialized yet, but the check in Parent
will access the Child
's override of the property when it does the check.
That might sound complicated, here are the relevant parts of the decompiled bytecode of your example (simplified):
public abstract class Parent {
@NotNull
private final String name;
@NotNull
public String getName() {
return this.name;
}
public Parent(@NotNull String name) {
super();
this.name = name;
if(!StringsKt.isBlank(this.getName())) { // uses getName, which is overridden in
// Child, so Child's field is returned
throw new IllegalArgumentException("Firstname must not be blank");
}
}
}
public final class Child extends Parent {
@NotNull
private final String name;
@NotNull
@Override
public String getName() {
return this.name;
}
public Child(@NotNull String name) {
super(name); // calls super constructor
this.name = name; // sets own name field
}
}