Passing Map with Non-Null keys to function that takes map with nullable keys?

Say I have a function:

fun doSomething( mapping: Map<String?,Boolean> ) 

And I have a map:

val mapping = mapOf( "One": true, "Two": false )

I can’t pass it in directly:

doSomething(mapping)

Because Kotlin says:

Required: Map<String?,Boolean>
Found: Map<String,Boolean>

I understand that a Map<String,Boolean> isn’t strictly the same thing as a Map<String?,Boolean> – so what’s the simplest way to convert the map so that I can pass it in? I feel like there ought to be a simple way to do this, but I haven’t figured out what the simple way is. :wink:

You have to either specify the type of mapping variable or call mapOf with explicit type parameters.

The problem is that the generic type for the key is invariant. You can however still pass the map like this

doSomething(mapping as Map<String?, Boolean>)

This will however generate an unchecked cast warning, which you can suppress.

1 Like

You have to either specify the type of mapping variable or call mapOf with explicit type parameters.

Yeah, sorry, the example is a simplification of reality. I have a Map<String,morestuffhere> that I get from somewhere else, I know how to make my own Map<String?,morestuffhere>, I’m just wondering what the cleanest way of converting the map I already have to the nullable-key version is.

You can however still pass the map like this

Huh – I could swear I tried a cast (yay type erasure), but I just tried again and, yes, it works. Weird. Not sure why my attempt before failed now. :wink:

2 Likes

Note that such a cast is unsafe. The implementation of doSomething() could call get(null) on the map (which would be perfectly legal, as its parameter explicitly allows a null key). The cast could then cause a null-pointer exception.

1 Like

The cast could then cause a null-pointer exception.

Sorry, where’s the potential NPE? The biggest potential problem to me is that doSomething() could insert a null into the map which the caller could then access, not expecting a potential null, but that’s always a bit of a risk with generics given type erasure.

>>> fun doSomething( mapping: Map<String?,Boolean> ) {
...   println("mapping[null]: ${mapping[null]}")
... }
>>> doSomething(mapOf(null to false))
mapping[null]: false
>>> doSomething(mapOf("A" to true) as Map<String?,Boolean>)
mapping[null]: null

This seems … not terribad.

1 Like

The problem could be if the map is not backed by a java map but by a custom implementation in kotlin. In that case mapping[null] will fail with a NPE.

class MyMap<K: Any, V: Any>(val m: Map<K, V>): Map<K, V> by m {
   override operator fun get(key: K): V? = m[key]  // kotlin will generate a null check for key, which would lead your code to fail
}

That said, as long as you are sure that you are only dealing with java standard library maps you should be fine.

1 Like

Fair enough.

In this case it’s definitely being created by mapOf(), which in its current implementation seems to be a LinkedHashMap, although there’s no guarantee that can’t change in the future, so there’s still some risk there in theory.

1 Like

For me it compiles, I had just seen it somewhere standard code:

fun doSomething(mapping: Map<out String?, Boolean>): Unit {}

val mapping = mapOf("One" to true, "Two" to false)
doSomething(mapping)

You are using Map<out String?, Boolean> instead of Map<String?, Boolean>. But you might not always be in controll of the function.

1 Like