在超类中需要'init块引发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)
谢谢你的时间
祝一切顺利
在name.isNotBlank()
,你应该得到像这样的皮棉警告:
在构造函数中访问非final属性名称
您正在构建Parent
时访问name
属性,但是,在Child
覆盖name
。 这个覆盖意味着在内部, Parent
类和Child
类都有专用的name
字段,而且由于Parent
构造函数是创建Child
对象时调用的第一个对象,因此Child
的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