Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map get with null key

I'm confused by Kotlin's null safety features when it comes to maps. I have a Map<String, String>. Yet I can call map.get(null) and it returns null to indicate that the key is not present in the map. I expected a compiler error, because map is a Map<String, String> and not a Map<String?, String>. How come I can pass null for a String argument?

And a related question: is there any type of Map, be it a stdlib one or a third-party implementation, that may throw NullPointerException if I call get(null)? I'm wondering if it is safe to call map.get(s) instead of s?.let { map.get(it) }, for any valid implementation of Map.

Update

The compiler does indeed return an error with map.get(null). But that is not because of null safety, but because the literal null doesn't give the compiler an indication of the type of the parameter being passed. My actual code is more like this:

val map: Map<String, String> = ...
val s: String? = null
val t = map.get(s)

The above compiles fine, and returns null. How come, when the key is supposed to be a String which is non-nullable?

like image 428
k314159 Avatar asked Mar 15 '26 00:03

k314159


1 Answers

The get method in Map is declared like this:

abstract operator fun get(key: K): V?

so for a Map<String, String>, its get method should only take Strings.

However, there is another get extension function, with the receiver type of Map<out K, V>:

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

The covariant out K is what makes all the difference here. Map<String, String> is a kind of Map<out String?, String>, because String is a subtype of String?. As far as this get is concerned, a map with dogs as its keys "is a" map with animals as its keys.

val notNullableMap = mapOf("1" to "2")
// this compiles, showing that Map<String, String> is a kind of Map<out String?, String>
val nullableMap: Map<out String?, String> = notNullableMap

And this is why you can pass in a String? into map.get, where map is a Map<String, String>. The map gets treated as "a kind of" Map<String?, String> because of the covariant out K.

And a related question: is there any type of Map, be it a stdlib one or a third-party implementation, that may throw NullPointerException if I call get(null)?

Yes, on the JVM, TreeMap (with a comparator that doesn't handle nulls) doesn't support null keys. Compare:

val map = TreeMap<Int, Int>()
println(map[null as Int?]) // exception!

and:

val map = TreeMap<Int, Int>(Comparator.nullsLast(Comparator.naturalOrder()))
println(map[null as Int?]) // null

However, note that since the problematic get is an extension function that is available on every Map, you cannot prevent someone from passing in a nullable thing to your map at compile time, as long as your map implements Map.

like image 115
Sweeper Avatar answered Mar 16 '26 15:03

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!