从超级类触发的Kotlin Init Block从其继承时具有空属性

4 jvm kotlin

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)
}
Run Code Online (Sandbox Code Playgroud)

上面的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)
Run Code Online (Sandbox Code Playgroud)

据我了解,从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)
}
Run Code Online (Sandbox Code Playgroud)

安慰 :

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)
Run Code Online (Sandbox Code Playgroud)

我在这里做错了还是这是语言错误... ??? 我该怎么做才能避免该错误,并使init块使用实际传递的值而不是null触发…… 详细说明幕后发生的事情。这时我在实际的代码库中遇到了类似这样的类,在这种情况下我想从另一个类继承,我想保持属性名称不变...

Tod*_*odd 6

本质上,因为您告诉Kotlin您的子类现在将要定义name,所以initSuper执行块插入时未定义它。您要推迟对它的定义,直到SubClass初始化为止。

此行为记录在Kotlin网站上的“派生类初始化顺序”下

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

...

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

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

  • 我相信这与“有效Java”中描述的问题类似:构造函数不应调用可覆盖对象http://www.javapractices.com/topic/TopicAction.do?Id=215 (2认同)
  • 似乎这篇文章已移至“继承”下的另一个部分 https://kotlinlang.org/docs/inheritance.html#driven-class-initialization-order (2认同)