An unexpected NPE shows up in my application when initialising an inheritor of a superclass utilising abstract val functions in its init block, this has me confused. Perhaps someone can explain why this is. FYI I solved the problem momentarily by using abstract functions instead, but I still do not understand why this happens.
My super class simply wraps another component to express the state better. In the init function there is a common enable function which can cause an immediate callback which would access the abstract vals set in the inheriting class. This causes an NPE and I do not know why, since the vals are overridden correctly in the inheriting class. Here is the code with some explaining comments of the issue:
abstract class SomeSuperClass(private val foundation: SomeFoundation) {
private val callback = object : SomeCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
onAvailable() // Accesses the inheritor which can cause an NPE on init
}
override fun onLost(network: Network) {
super.onLost(network)
onLost()
}
}
init {
val manager: SomeManager = SomeManager()
manager.registerCallback(callback) // Can cause an immediate callback, this is probably why the NPE happens rarely, since it does not always cause an immediate callback.
}
abstract val onAvailable: () -> Unit
abstract val onLost: () -> Unit
}
/** Singleton inheritor. */
class SomeInheritingObject private constructor(): SomeSuperClass(foundation = SomeFoundation()) {
private val _state: MutableStateFlow<State> = MutableStateFlow(State.Initial)
val state: StateFlow<State> = _state
// This overriden val is not allocated when super.init is called, why?
override val onAvailable: () -> Unit = {
_state.value = State.Available
}
override val onLost: () -> Unit = {
_state.value = State.Unavailable
}
// This is a singleton component
companion object {
private val observer: SomeInheritingObject by lazy { SomeInheritingObject() }
fun getInstance(): SomeInheritingObject = observer
}
}
I expect the overridden abstract function values to be set in super.init, perhaps they are not. In that case I'd appreciate if someone would refer me to some documentation.
You are right. The overridden
valis not initialised whensuper.initis called. The initialisation order is specified in the spec here:In your case, there is no secondary constructor or interface delegation, so steps 2 and 5 are omitted. The crucial thing though, is that step 4 occurs after step 1. Everything in the superclass is initialised first, before any of the initialisation code in the subclass is run, i.e. this:
so while you are still in the superclass
init, none of the above are initialised. On the JVM, this means they are null.I suppose you mean:
I don't think that actually solves the problem though, because if the callback is called immediately,
_statewould still be null.In any case, I would suggest that you redesign your code to avoid using overridable members from places where subclasses are not fully initialised, like in superclass constructors. Those members are very likely to assume that all the members all initialised, like in this case here.