Generics question, I’ve defined an interface for m...
# getting-started
z
Generics question, I’ve defined an interface for my factories and I’m trying to create a mapping so I can pass in the class of an Event & get back the factory which can create it.
Copy code
interface SchemaFactory<in E: Event, out S: Schema> {

  fun create(event: E) : S
}

class TestEventFactory : SchemaFactory<TestEvent, TestEventSchema>

private val factories: Map<Class<out Event>, SchemaFactory<Event, Schema>> = mapOf(TestEvent::class.java to TestEventFactory())
However I get the following error message on `factories`:
Copy code
Type mismatch.
Required:
Map<Class<out Event>, SchemaFactory<Event, Schema>>
Found:
Map<Class<out Event>, TestEventFactory>
r
Because
TestEventFactory
is not a
SchemaFactory<Event, Schema>
(due to the
in
variance)
Your map values are factories that can take in any event and produce a schema, but
TestEventFactory
cannot take in any event, only `TestEvent`s.
z
how would I define a map of factories that can only take in an event which matches their key in the map?
r
You'll likely just need to do some unsafe casting. For example:
Copy code
class AllFactories {
    private val factories: Map<Class<out Event>, SchemaFactory<*, *>> =
        mapOf(TestEvent::class.java to TestEventFactory())

    operator fun <E : Event> get(type: Class<out E>): SchemaFactory<E, Schema> {
        @Suppress("UNCHECKED_CAST")
        return factories[type] as SchemaFactory<E, Schema>
    }
}
z
interesting, I’ll give this a shot. thank you!
r
Just make sure you take care of matching the types, since the compiler won't be able to check. You'll start getting some ugly runtime bugs if you're not careful.
z
is there a safer approach to this? seems like a somewhat common usecase
r
It depends on what you mean by "safer". The
Map
interface does not enforce any sort of relation between the type of the keys and values, so there's no way to say "a key of Class<T> will always point to a value of T". You can make your own type to do that (which is essentially what
AllFactories
is, it just happens to use a
Map
for an easy implementation). In the end, there will likely have to be an "unsafe" cast somewhere. "Unsafe" doesn't mean it isn't safe per say, just that the compiler cannot guarantee it's safe, so it's on you to do so.
ArrayList
has to do this, for example, as under the covers it's using
Array<Any>
(I guess
Object[]
on the JVM since it's a Java class), so the
get(Int)
calls basically do
elements[index] as T
, which is also an "unsafe" cast.
z
I see, I guess I meant is there a standard approach to what I’m trying to do
r
From my experience (though I'm just one person, so take it with a grain of salt), that is the standard approach. While it may seem like a common use case, it's one of those things that usually ends up quite dependent on implementation details. For example, in practice there ends up being a fairly significant difference between
Map<Class<T>, T>
and
Map<Class<T>, Item<T>>
(that is, a map from classes to values of the class type vs from classes to some other type that happens to share a generic type with the class), so there isn't really a one size fits all solution.
z
fair enough! thank you for your time and all this detail 🙂
👍 1
e
there are type-safe maps, but they can't be
Map
CoroutineContext/.Element/.Key is one of them in stdlib