Let say that I have a Map for translating a letter of a playing card to an integer
val rank = mapOf("J" to 11, "Q" to 12, "K" to 13, "A" to 14)
When working with the map it seems that I always have to make a null safety check even though the Map and Pair are immutable:
val difference = rank["Q"]!! - rank["K"]!!
I guess this comes from that generic types have Any? supertype. Why can't this be resolved at compile time when both Map and Pair are immutable?
It is not about the implementation of Map (being it Kotlin or Java based). You are using a Map and a map may not have a key hence [] operator returns nullable type.
mapOf() is providing a Map with no guarantees for the presence of a key-- something which is kind of expected especially considering the Java implementation of Map.
While I might personally prefer sticking with null-safe calls and elvis operators, it sounds like you'd prefer cleaner code at the call site (especially considering you know these keys exist and have associated non-null values). Consider this:
class NonNullMap<K, V>(private val map: Map<K, V>) : Map<K, V> by map {
override operator fun get(key: K): V {
return map[key]!! // Force an NPE if the key doesn't exist
}
}
By delegating to an implementation of map, but overriding the get method, we can guarantee that return values are non-null. This means you no longer have to worry about !!, ?., or ?: for your usecase.
Some simple test code shows this to be true:
fun main(args: Array<String>) {
val rank = nonNullMapOf("J" to 11, "Q" to 12, "K" to 13, "A" to 14)
val jackValue: Int = rank["J"] // Works as expected
println(jackValue)
val paladinValue: Int = rank["P"] // Throws an NPE if it's not found, but chained calls are considered "safe"
println(jackValue)
}
// Provides the same interface for creating a NonNullMap as mapOf() does for Map
fun <K, V> nonNullMapOf(vararg pairs: Pair<K, V>) = NonNullMap(mapOf<K, V>(*pairs))