Why can’t I pass a value of type `Map<String, S...
# announcements
r
Why can’t I pass a value of type
Map<String, String?>
as parameter of a function taking
Map<Any?, *>
? I assume I can safely do an unchecked cast here, but I wonder why I need it at all
s
Map is defined as
Map<K, out V>
. The key type is invariant…
The map is invariant on its key type, as it can accept key as a parameter (of containsKey for example) and return it in keys set.
s
If you redefine the function’s parameter’s type to
Map<out Any?, *>
, it will work
r
I don’t control the function. An unchecked cast would still work, right?
a
or you can do manually unchecked cast
source as Map<Any?, *>
r
Thanks for the explanation.
s
An unchecked cast would still work, right?
Yep, but it may not be safe, depending on your code (ie the compiler cannot guarantee there won’t be any classcast-exceptions) in your code when dealing with your map.
k
Yes it's unsafe, a specific example:
Copy code
class MyMap: Map<String, Int> {
    override fun containsKey(str: String) = ...
    //...
}
r
I know what an unchecked cast is, but I want to be sure that in this specific case a classcast exception isn’t possible, as I’m casting subtypes of expected types
k
Will error if you cast it to
Map<Any, Int>
and call
containsKey(5)
.
r
I see, so I should create another map
Something like this
Copy code
val wrongType: Map<String, String?>
val rightType: Map<Any?, *> = HashMap(wrongType)
Actually looks like I can call my function with
HashMap(wrongType)
as parameter directly and I have no warning, it’s also shorter than the cast
k
Then it's safe simple smile
r
I guess
LinkedHashMap
has more chances to be closer to the original map than
HashMap
, so I’ll use that instead
k
.toMap()
preserves iteration order too.
a
I think in your case it is safe and creation of new instance of hash map is redundant. Just use unchecked cast and add @Suppress("UNCHECKED_CAST") annotation. But if source map could be mutated in the method it would be good reason to create new instance of map.
k
No it's not safe, that's exactly what my example shows.
r
Yes it’s unsafe, I have no clue about the implementation of
Map<String, String?>
that I have in the first place, so what @karelpeeters demonstrated could happen
i
Will error if you cast it to
Map<Any, Int>
and call
containsKey(5)
.
@karelpeeters it won't, it just returns false: https://pl.kotl.in/S1LBE6kKE
k
In that example I made my own map implementation, that should error, right?
i
No, these methods have special bridge implementation to adhere to JVM Map contract, where containsKey and containsValue accept Object parameter
r
Ah, so it worked in fact!
myMethod(LinkedHashMap(myMap))
is cleaner than
myMethod(myMap as Map<Any?, *>)
anyway. And the second option requires a warning suppression.
k
@ilya.gorbunov Huh that's very interesting, I never would have guessed that. Is that documented anywhere?
i
No, that's a hidden knowledge 🤐. I believe we'll mention it at least in the language spec.