Kotlin Generics: type mismatch in shared map option

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) 

he gives an error:

Type mismatch. The expected value of 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 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 a similar type mismatch error.

How to declare generics so that I can use any class that implements ValueConverter and accepts any class that extends the event?

+5
source share
1 answer

The error in this line is:

private val converters = HashMap<String, ValueConverter<Event>>()

The values ​​of this map are limited by ValueConverter<Event> . Therefore, if you have a class

class FooEvent : Event

and value converter:

ValueConverter<FooEvent> ,

You could not save the value converter on your map. What you really want is the type of projection of the star * .

private val converters = HashMap<String, ValueConverter<*>>()

Now you can put any value converter on the map.


However, this opens up another problem: how

fun <E : Event> convert(event: E): Float

to know what is the general type of return converter on the card? In the end, a map can contain several converters for different types of events!

IntelliJ quickly complains:

Out-projected type 'ValueConverter <*>?' prohibits the use of 'public abstract fun convert (event: E): Float defined in ValueConverter'.

But you already know the generic type, because your card key is the name of a parameter of the type type!

So just translate the return value using the map:

 @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 wondering why the compiler has not complained about your converter function before: remember how your map could contain only ValueConverter<Event> and only this class? This means that the compiler knew that you could pass any subclass of Event to this converter. After you change the type of star, the compiler does not know if it could be ValueConverter<FooEvent> or ValueConverter<BazEvent> , etc. - make an effective function signature of this converter on your card convert(event: Nothing) : <w>
because nothing is a valid entry.

+8
source

Source: https://habr.com/ru/post/1261136/


All Articles