超级类中的Kotlin Init Block在从它继承时使用null属性

时间:2018-04-09 14:39:55

标签: kotlin jvm

open class Super {

    open var name : String = "Name1"

    init {
        println("INIT block fired with : $name")
        name = name.toUpperCase()
        println(name)
    }

}

class SubClass(newName : String) : Super() {

    override var name : String = "Mr. $newName"

}

fun main(args: Array<String>) {

    var obj = SubClass("John")
    println(obj.name)
}

上述Kotlin代码导致以下TypeCastException:

INIT block fired with : null
Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type java.lang.String
    at Super.<init>(index.kt:7)
    at SubClass.<init>(index.kt:13)
    at IndexKt.main(index.kt:21)

正如我在Kotlin中继承一个类时所理解的那样,首先使用传递的参数调用超类的主构造函数和init块以及辅助构造函数。之后,子类可以使用自己的版本覆盖这些属性。

那么为什么上面的代码会导致所描述的异常...我做错了什么...为什么超类中的init块被 null 触发......? ?起初我猜测init块可能在实际属性初始化之前被触发,因为它作为主构造函数的一部分执行,但是在主构造函数中初始化name属性如下所示给出了相同的错误,IDE会警告我如此。

open class Super(open var name : String = "Name1") {

    init {
        println("INIT block fired with : $name")
        name = name.toUpperCase()
        println(name)
    }

}

class SubClass(newName : String) : Super() {

    override var name : String = "Mr. $newName"

}

fun main(args: Array<String>) {

    var obj = SubClass("John")
    println(obj.name)
}

控制台:

INIT block fired with : null
Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type java.lang.String
    at Super.<init>(index.kt:5)
    at Super.<init>(index.kt:1)
    at SubClass.<init>(index.kt:11)
    at IndexKt.main(index.kt:19)

我在这里做错了还是这是一个语言错误...... ???我该怎么做才能避免错误,并使用实际传递的值激活init块,而不是 null ...... ???详细说明幕后发生的事情。这时我在实际的代码库中有类似这样的类的几种情况,我想从其他类继承,我想维护属性名称......

2 个答案:

答案 0 :(得分:5)

基本上,因为您告诉Kotlin您的子类现在将定义name,所以在执行init中的Super块时不会定义它。在SubClass初始化之前,您将推迟定义。

此行为记录在Kotlin网站"Derived class initialization order"下:

  

在构造派生类的新实例期间,基类初始化作为第一步完成(仅在评估基类构造函数的参数之前),因此在派生类的初始化逻辑之前发生运行

     

...

     

这意味着,在基类构造函数执行时,尚未初始化在派生类中声明或覆盖的属性。如果在基类初始化逻辑中使用任何这些属性(直接或间接,通过另一个重写的开放成员实现),则可能导致不正确的行为或运行时失败。 设计基类时,应避免在构造函数,属性初始值设定项和初始化块中使用开放成员。 [强调我的]

FWIW,这类似于某些Java代码分析工具在构造函数中引用非final方法时会抱怨的原因。在Kotlin中解决这个问题的方法是不要在超类中的open块中引用init属性。

答案 1 :(得分:0)

有同样的问题,kotlin 的一个恶心问题,当超类调用内部方法后忽略或初始化子类构造函数时,这不是一件安全的事情,如果不是我在 kotlin 中发现的最糟糕的事情。