问题
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)
}
The above Kotlin code results in the following 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)
As my understanding goes while inheriting from a class in Kotlin, first the primary constructors and init blocks and secondary constructors of super classes are called with passed parameters. After which the subclass can override such properties with its own version.
Then why does the above code results in the exception as described ... What am I doing wrong ... Why does the init block in super class is fired with null ...??? At first my speculation was that the init block might get fired before the actual property initialization as it is executed as a part of primary constructor but initializing the name property in the primary constructor as below gives the same error and the IDE would have warned me if so.
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)
}
Console :
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)
Am I doing something wrong here or is this a language bug...??? What can I do to avoid the error and to make the init blocks fire with the actual passed value and not null ... ??? Elaborate what is happening behind the scene. At this time I have several situations with classes like this in my actual codebase where I want to inherit from another classes, I want to maintain property names as they are...
回答1:
Essentially, because you tell Kotlin that your subclass is going to be defining name
now, it is not defined when the init
block in Super
is executed. You are deferring definition of that until the SubClass
is initialized.
This behavior is documented on the Kotlin website under "Derived class initialization order":
During construction of a new instance of a derived class, the base class initialization is done as the first step (preceded only by evaluation of the arguments for the base class constructor) and thus happens before the initialization logic of the derived class is run.
...
It means that, by the time of the base class constructor execution, the properties declared or overridden in the derived class are not yet initialized. If any of those properties are used in the base class initialization logic (either directly or indirectly, through another overridden open member implementation), it may lead to incorrect behavior or a runtime failure. Designing a base class, you should therefore avoid using open members in the constructors, property initializers, and init blocks. [emphasis mine]
FWIW, this is similar to the reason that some Java code analysis tools will complain if you refer to non-final methods in a constructor. The way around this in Kotlin is to not refer to open
properties in your init
blocks in the superclass.
来源:https://stackoverflow.com/questions/49735604/kotlin-init-block-in-super-class-firing-with-null-properties-when-inheriting-fro