在超类'init块中需要引发IllegalArgumentException [Kotlin]

时间:2017-11-09 10:08:25

标签: kotlin kotlin-null-safety

早安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 个答案:

答案 0 :(得分:2)

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

  

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

您正在构建name时访问Parent媒体资源,但name会覆盖Child。此覆盖意味着在内部,ParentChild类都具有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

相关问题