Требование в блоке инициализации суперкласса вызывает IllegalArgumentException [Kotlin]
Доброе утро, котлинские гуру.
У меня есть структура наследования, в которой абстрактный суперкласс реализует некоторые общие проверки данных. Компилятор не жалуется, но после выполнения JVM создает исключение IllegalArgumentException
Код
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)
Спасибо за ваше время
всего наилучшего
2 ответа
На name.isNotBlank()
, вы должны получить предупреждение Lint, как это:
Доступ к неконечному имени свойства в конструкторе
Вы получаете доступ к name
собственность в то время, когда Parent
строится, однако, name
переопределяется в Child
, Это переопределение означает, что внутри Parent
а также Child
классы имеют частные поля для name
и так как Parent
Конструктор это первое, что называется, когда Child
создан, Child
"s name
пока не будет инициализирован, но регистрация Parent
будет иметь доступ к Child
переопределение свойства при проверке.
Это может показаться сложным, вот соответствующие части декомпилированного байт-кода вашего примера (упрощенно):
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
}
}
Некоторый код для понимания порядка выполнения инициализаторов kotlin [1]
open class Parent {
private val a = println("Parent.a")
constructor(arg: Unit=println("Parent primary constructor default argument")) {
println("Parent primary constructor")
}
init {
println("Parent.init")
}
private val b = println("Parent.b")
}
class Child : Parent {
val a = println("Child.a")
init {
println("Child.init 1")
}
constructor(arg: Unit=println("Child primary constructor default argument")) : super() {
println("Child primary constructor")
}
val b = println("Child.b")
constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument")): this() {
println("Child secondary constructor")
}
init {
println("Child.init 2")
}
}
Выход для ребенка (1)
Child secondary constructor default argument
Child primary constructor default argument
Parent primary constructor default argument
Parent.a
Parent.init
Parent.b
Parent primary constructor
Child.a
Child.init 1
Child.b
Child.init 2
Child primary constructor
Child secondary constructor
По сути, Parent.init вызывается до того, как реальный объект создан, и, следовательно, создается исключение.
Ссылка: [1] https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546