I have: ```private val actions = mutableMapOf<S...
# getting-started
d
I have:
Copy code
private val actions = mutableMapOf<String, SomeInterface>()

fun <T : SomeInterface> getOrNull(key: String): T? =
        actions[key] as? T

val tryToGetAFoo = getOrNull<Bar>("something")
// I have a Foo in the "something" key so this should be null... Bar and Foo are both implementation of SomeInterface...
what is this error that I'm getting:
class Bar cannot be cast to class Foo (Bar and Foo are in unnamed module of loader 'app')
?
c
Would be expected. A
Foo
is not a
Bar
even though they implement the same interface. The
as? T
combined with
getOrNull<Bar>
casts everything to a
Bar
.
d
But in the docs it says that the
as?
operator returns null if it doesn't succeed in casting...
j
And what does the compiler warning says on that line?
This cast is unchecked, meaning that at runtime there is no cast at all, so it won't behave as you expect
d
class Bar cannot be cast to class Foo (Bar and Foo are in unnamed module of loader 'app')
c
In Kotlin playground: val tryToGetAFoo = getOrNull<Bar>(“something”)
d
Right, that's exactly the error...
j
@dave08 the thing you wrote here is not the compiler warning, that's the runtime error you get. The warning should be something like
Unchecked cast: SomeInterface? to T
. This means that even though you're writing an explicit cast here with
as?
, it will not check the type of the value at runtime, so the cast will never fail and the value will always be returned as-is. That is why the function happily returns a
Foo
even when called with
<Bar>
. The cast that fails is probably not the one you wrote with
as?
, but the one the compiler does automatically afterwards during the variable assignment. To fix it, you can use an inline function with
reified T
, so the cast is not unchecked:
Copy code
private inline fun <reified T : SomeInterface> getOrNull(key: String): T? =
    actions[key] as? T
https://pl.kotl.in/LgsspJQUH
c
Yep. It works with reification:
Copy code
inline fun <reified T : SomeInterface> getOrNull(key: String): T? =
        actions[key] as? T
d
Yeah, but if you noticed in the question
actions
is private in the class... so an inline function won't work here...
j
Then make it
@PublishedApi internal
c
or have a helper function to bridge the visibility gap.
d
or have a helper function
how? I can't cast to a KClass...?
c
d
Then make it
@PublishedApi internal
It's used in the same module..
Oh, that's good...
j
If you don't want to make
actions
visible to the module, you can either provide an untyped helper as suggested by Chris, or make a typed helper that uses a
KClass
, and then a reified helper for convenience:
Copy code
inline fun <reified T : SomeInterface> getOrNull(key: String): T? = getOrNull(key, T::class)

fun <T : SomeInterface> getOrNull(key: String, type: KClass<T>): T? =
    type.safeCast(actions[key])
👍🏼 1
👍 1
d
Does that
type.safeCast
require the reflection libs?
j
Ah maybe, I just tried on the playground, but it's in
kotlin.reflect.full
so there is a chance, yes
Maybe Chris's option is better for your case, but I'm not sure it's much better than just exposing
actions
as a read-only map, to be frank 😄
d
True in general, but here I'm abstracting away the fact that the key is a value class internally.
c
exposing the map as readonly also allows changing
getOrNull
to be an extension function for a more natural feel.
Copy code
val actions = mutableMapOf<String, SomeInterface>()

interface SomeInterface {
    
}

class Foo : SomeInterface {
    
}

class Bar: SomeInterface {
    
}

inline fun <reified T : SomeInterface> Map<String,SomeInterface>.getOrNull(key: String): T? =
        actions[key] as? T

//public fun getAction(key : String) : SomeInterface? {
//    return actions[key]
//}

// I have a Foo in the "something" key so this should be null... Bar and Foo are both implementation of SomeInterface...


/**
 * You can edit, run, and share this code.
 * <http://play.kotlinlang.org|play.kotlinlang.org>
 */
fun main() {
    actions["foo"] = Foo()
    actions["bar"] = Bar()
	val tryToGetAFoo = actions.getOrNull<Bar>("foo")

}
👍🏼 1
True in general, but here I’m abstracting away the fact that the key is a value class internally.
Is it really abstracted away, though? Having to pass in a key to getOrNull exposes that there’s a key/value relationship.
d
Yeah, but outside of this class I need to allow access w/o wrapping the key everywhere I need to access it's contents. If I'd be passing around the wrapped key (which might happen in the future), then I'd probably change that.
👍 1
j
What's the point of wrapping the key if not to make consumers type-safe?
I would have expected the opposite: internally you can use raw values, but for your public API you want better types for your consumers safety
d
I'm refactoring... so I need to do things step by step, I don't want to break too much and have to rewrite all the unit tests at once...
j
ah ok, it sounded like an actual design decision, not a temporary state for the refac. Then I understand 🙂