Your code works if you change the properties to have getters that return the strings, rather than have them initialised with the strings:
open class ParentClass {
protected open val example get() = ""
val valToInitialize = "Hello $example"
fun printValue() {
print(valToInitialize)
}
}
class ChildClass : ParentClass() {
override val example get() = "World"
}
This is because initialisation happens in the order of "parent -> child", so if your properties are not initialised, but rather computed in the getter, you circumvent the problem.
If you don't actually intend on instantiating any Parent instances, you can make the parent class and the property abstract, so that child classes are forced to override it:
abstract class ParentClass {
protected abstract val example: String
...
}
Important: when implementing the property in child classes, avoid using any other instance properties or methods, because when example is accessed, the parent class might not be fully initialised yet, so the properties may have values you may not expect, and the methods may behave in unexpected ways as a result. (basically this exact question!)
Also consider not using inheritance at all, then you don't have to worry about child classes accidentally using uninitialised properties in their implementation of example:
fun main(){
val x = ParentClass(ChildClass())
x.printValue()
}
class ParentClass(val childClass: ChildClassInterface) {
val valToInitialize = "Hello ${childClass.example}"
fun printValue() {
print(valToInitialize)
}
}
interface ChildClassInterface {
val example: String
}
class ChildClass: ChildClassInterface {
override val example = "Foo"
}