I have the following code that uses generics:
abstract class Event(val name: String)
interface ValueConverter<E : Event> {
fun convert(event: E): Float
fun getEventClass(): Class<E>
}
class ValueConverters {
private val converters = HashMap<String, ValueConverter<Event>>()
fun <E : Event> register(converter: ValueConverter<E>) {
converters.put(converter.getEventClass().name, converter)
}
fun unregister(eventClass: Class<Event>) {
converters.remove(eventClass.name)
}
fun <E : Event> convert(event: E): Float {
return converters[event.javaClass.name]?.convert(event) ?: 0.0f
}
fun clear() {
converters.clear()
}
}
But on this line:
converters.put(converter.getEventClass().name, converter)
it gives an error:
Type mismatch. Expected ValueConverter<Event>. Found ValueConverter<E>.
I also tried something like this:
class ValueConverters {
private val converters = HashMap<String, ValueConverter<Event>>()
fun register(converter: ValueConverter<Event>) {
converters.put(converter.getEventClass().name, converter)
}
fun unregister(eventClass: Class<Event>) {
converters.remove(eventClass.name)
}
fun convert(event: Event): Float {
return converters[event.javaClass.name]?.convert(event) ?: 0.0f
}
fun clear() {
converters.clear()
}
}
But the problem is when calling ValueConverters.register()
with something like:
class SampleEvent1 : Event(name = SampleEvent1::class.java.name)
class SampleValueConverter1 : ValueConverter<SampleEvent1> {
override fun convert(event: SampleEvent1): Float = 0.2f
override fun getEventClass(): Class<SampleEvent1> = SampleEvent1::class.java
}
converters.register(converter = SampleValueConverter1())
It also gives the similar Type mismatch error.
How should I declare the generics so that I could use any class that implements ValueConverter and accepts any class that extends Event?
The error is on this line:
private val converters = HashMap<String, ValueConverter<Event>>()
The values of this map are limited to ValueConverter<Event>
. So if you have a class
class FooEvent : Event
and a value converter:
ValueConverter<FooEvent>
,
you couldn't store that value converter in your map. What you would actually want is a *
star projection type.
private val converters = HashMap<String, ValueConverter<*>>()
Now you can put whatever value converter in the map.
However, this uncovers another problem: How does
fun <E : Event> convert(event: E): Float
know what the generic type of the returned converter in the map is? After all, the map might contain multiple converters for different event types!
IntelliJ promptly complains:
But you already know the generic type because your map key is the name of the generic type parameter!
So simply cast the returned value by the map with force:
@Suppress("UNCHECKED_CAST")
fun <E : Event> convert(event: E): Float {
val converter = converters[event.javaClass.name] ?: return 0.0f
return (converter as ValueConverter<E>).convert(event)
}
If you are curious why the compiler did not complain about your converter function earlier: Remember how your map could only hold ValueConverter<Event>
and only that class? This means that the compiler knew you could pass any subclass of Event
into this converter. Once you changed to a star projection type, the compiler doesn't know if this might be a ValueConverter<FooEvent>
, or a ValueConverter<BazEvent>
, etc. - making the effective function signature of a given converter in your map convert(event: Nothing)
:
because nothing is a valid input.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With