2016-12-12 14 views
5

Mam następujący kod, który używa rodzajowych:Kotlin za rodzajowych: Niezgodność typu parametru rodzajowy mapy

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

} 

Ale na tej linii:

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

daje błąd:

Type mismatch. Expected ValueConverter<Event>. Found ValueConverter<E>.

Próbowałem również coś takiego:

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

} 

Ale problem jest, gdy dzwoni ValueConverters.register() z czymś takim:

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

Daje on również podobny błąd niezgodność typów.

W jaki sposób powinienem zadeklarować generyczne, aby móc korzystać z dowolnej klasy, która implementuje ValueConverter i akceptuje dowolną klasę rozszerzającą Wydarzenie?

Odpowiedz

8

Błąd znajduje się na tej linii:

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

Wartości tej mapie są ograniczone do ValueConverter<Event>. Więc jeśli posiadają klasę

class FooEvent : Event

a wartość Przelicznik:

ValueConverter<FooEvent>,

nie można przechowywać tę wartość konwertera na mapie. To, czego byś chciał, to typ projekcji gwiazdkowej *.

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

Teraz można umieścić niezależnie od konwertera wartość na mapie.


to jednak odkrywa kolejny problem: jak

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

wiedzieć, co generyczny typ zwracanej konwertera w mapie jest? W końcu mapa może zawierać wiele konwerterów dla różnych typów zdarzeń!

IntelliJ niezwłocznie narzeka:

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

Ale wiesz już typ rodzajowy ponieważ klucz mapa jest nazwa parametru typu rodzajowego!

Więc po prostu rzucić wartość zwracaną przez mapę z życie:

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

Jeśli jesteś ciekaw, dlaczego kompilator nie narzekać swojej funkcji przetwornika wcześniej: pamiętam jak twoja mapa mógł tylko trzymać ValueConverter<Event> i tylko ta klasa? Oznacza to, że kompilator wiedział, że do tego konwertera można przekazać podklasę o numerze Event. Gdy zmienia się rodzaj projekcji gwiazda, kompilator nie wie, czy to może być ValueConverter<FooEvent>, lub ValueConverter<BazEvent>, itp - wykonanie skutecznej Funkcja Podpis danego konwertera w swojej mapie convert(event: Nothing):

bo nic jest poprawnym wprowadzeniem.

+0

Dzięki! To działa. –

Powiązane problemy