Kotlin Map with non null values

18,776

Solution 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.

Solution 2

There is another method for getting not null value from map:

fun <K, V> Map<K, V>.getValue(key: K): V

throws NoSuchElementException - when the map doesn't contain a value for the specified key and no implicit default value was provided for that map.

but operator for get == map[] returns nullable.

operator fun <K, V> Map<out K, V>.get(key: K): V?

Solution 3

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))
Share:
18,776
Tomas Karlsson
Author by

Tomas Karlsson

Updated on June 22, 2022

Comments

  • Tomas Karlsson
    Tomas Karlsson almost 2 years

    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?