在超类的 init 块中要求引发 IllegalArgumentException [Kotlin]

Require in superclass' init block raises IllegalArgumentException [Kotlin]

早上好 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(),您应该会收到这样的 lint 警告:

Accessing non-final property name in constructor

您正在构建 Parent 时访问 name 属性,但是 nameChild 中被覆盖。此覆盖意味着在内部,ParentChild 类 都有 name 的私有字段,并且由于 Parent 构造函数是当Child 已创建,Childname 尚未初始化,但 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")
    }
   }

Child(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 在构造实际 object 之前被调用,因此抛出异常。

参考: [1] https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546