Hi everyone.
I just stumbled over a code construct which let’s me wonder why it causes a NullPointerException
.
class Container {
public var lookup:HashMap<Int, String?>? = null
public fun initMap() {
val lookup = HashMap<Int, String?>()
lookup[1] = "Test"
lookup[2] = null
this.lookup = lookup
}
}
fun getContainer(): Container? {
val rnd = kotlin.random.Random.nextDouble()
return when {
rnd < 0.3 -> { // container with map
val container = Container()
container.initMap()
container
}
else -> null
}
}
fun getText(i:Int): String? {
return getContainer()?.lookup!![i]
}
fun main() {
println(getText(1) ?: "null")
println(getText(2) ?: "null")
}
My overall code is a bit more complex and has more variations but it this stripped version shows my concern. Running this code, get’s me a NullPointerException in the getText method. Both the Container
and the lookup
are potentially null. For the getContainer
I have null-safety because I expect at this place it could be null. But for the lookup
I know that it is not null in this case, so I use the !!
to handle the access.
I am aware that the overall code could maybe be rewritten to maybe satisfy Kotlin, but that’s not my intention. I would like to understand why Kotlin seems to be accessing lookup on a null member with such a constellation. I am transpiling some other codebase to Kotlin and would need to handle such special accesses, for this I need to understand the logic when Kotlin accesses members despite the ?.
operator.
In most other languages the ?.
access to the container would already ensure that the overall expression is null knowing no access to lookup
can be performed. But it seems that Kotlin tries to access lookup
anyhow and this causes a runtime error which seems not to be valid.
Other languages (e.g. C# and TypeScript):
- getContainer() is called and returns null
- Due to the fact of getContainer returning null the member access expression is already skipped and the overall expression is null.
In Kotlin:
- getContainer() is called and returns null.
- The member access to lookup is done, but due to null-safe this the expression
getContainer()?.lookup
evaluates to null. - The not-null expression
!!
is evaluated on null and this causes the NPE
If my assumption is correct I would like to understand what’s the rationale behind evaluating the overall expression further despite knowing already that the result is null.
Based on that I will need to update my transpiler, even though I don’t know yet how an alternative expression could look like to handle the two nullability cases within one access chain. Likely I need to promote all !! to ? accesses if there is a previous ? access in the chain.