Kotlin Map with non null values

2019-02-17 17:22发布

问题:

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?

回答1:

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.



回答2:

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))