0

早安 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)

谢谢你的时间

一切顺利

4

2 回答 2

2

在 上name.isNotBlank(),您应该会收到这样的 lint 警告:

在构造函数中访问非最终属性名称

您在构建name时访问该属性,但是,在. 这种覆盖意味着在内部, the和类都有私有字段,并且由于构造函数是在创建a 时首先调用的东西,因此'还不会被初始化,但签入将访问' 的覆盖进行检查时的财产。ParentnameChildParentChildnameParentChildChildnameParentChild

这听起来可能很复杂,以下是您的示例的反编译字节码的相关部分(简化):

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
    }
}
于 2017-11-09T11:05:08.847 回答
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

于 2017-11-09T11:25:42.890 回答