早安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)
感谢您的时间
一切顺利
答案 0 :(得分:2)
在name.isNotBlank()
上,您应该收到这样的lint警告:
在构造函数中访问非最终属性名称
您正在构建name
时访问Parent
媒体资源,但name
会覆盖Child
。此覆盖意味着在内部,Parent
和Child
类都具有name
的私有字段,并且因为Parent
构造函数是Child
时调用的第一个内容} {已创建},Child
&#39; name
尚未初始化,但是Parent
中的签入将访问Child
&#39}。在检查时覆盖属性。
这可能听起来很复杂,这里是你的例子的反编译字节码的相关部分(简化):
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
}
}
答案 1 :(得分:2)
一些代码来理解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