Frage Kotlins Generics: Typabweichung in generischen Map-Parametern


Ich habe den folgenden Code, der Generics verwendet:

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()
    }

}

Aber auf dieser Linie:

converters.put(converter.getEventClass().name, converter)

es gibt einen Fehler:

Typenkonflikt Erwarteter ValueConverter <Ereignis>. Gefunden ValueConverter <E>.

Ich habe auch so etwas versucht:

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()
    }

}

Aber das Problem ist beim Anrufen ValueConverters.register() mit etwas wie:

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())

Es gibt auch den ähnlichen Type-Mismatch-Fehler.

Wie sollte ich die Generika so deklarieren, dass ich jede Klasse verwenden kann, die ValueConverter implementiert und jede Klasse akzeptiert, die Event erweitert?


5
2017-12-12 13:40


Ursprung


Antworten:


Der Fehler ist in dieser Zeile:

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

Die Werte dieser Karte sind begrenzt auf ValueConverter<Event>. Also wenn du eine Klasse hast

class FooEvent : Event

und ein Wertkonverter:

ValueConverter<FooEvent>,

Sie konnten diesen Wertkonverter nicht in Ihrer Map speichern. Was du eigentlich willst, ist ein * Sternprojektionstyp.

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

Jetzt können Sie den Wertkonverter in die Karte einfügen.


Dies deckt jedoch ein anderes Problem auf: Wie geht es?

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

wissen, was der generische Typ des zurückgegebenen Konverters in der Karte ist? Schließlich enthält die Karte möglicherweise mehrere Konverter für verschiedene Ereignistypen!

IntelliJ beschwert sich sofort:

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

Aber Sie kennen den generischen Typ bereits, weil Ihr Map-Schlüssel der Name des generischen Typparameters ist!

Übergeben Sie einfach den zurückgegebenen Wert mit der erzwungenen Karte an die Karte:

@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)
}

Wenn Sie neugierig sind, warum der Compiler nicht früher über Ihre Konverterfunktion geklagt hat: Denken Sie daran, wie Ihre Karte nur halten konnte ValueConverter<Event> und nur diese Klasse? Dies bedeutet, dass der Compiler wusste, dass Sie jede Unterklasse von Event in diesen Konverter. Sobald Sie zu einem Sternprojektionstyp gewechselt haben, weiß der Compiler nicht, ob dies der Fall sein könnte ValueConverter<FooEvent>, oder ein ValueConverter<BazEvent>usw. - die effektive Funktionssignatur eines bestimmten Konverters in Ihrer Map erstellen convert(event: Nothing):

weil nichts eine gültige Eingabe ist.


8
2017-12-12 14:44