I recently tried the following in Kotlin.
The idea is that I will receive as input an Item
(like AmericanItem
for instance) that extends BaseItem
.
I am trying to have a different parser for each of theses items
Here is a sample code
abstract class BaseItem
class AmericanItem : BaseItem()
class EuropeanItem : BaseItem()
interface ItemParser<T : BaseItem> {
fun parse(item: T)
}
class AmericanItemParser : ItemParser<AmericanItem> {
override fun parse(item: AmericanItem) {
println("AmericanItemParser")
}
}
class EuropeanItemParser : ItemParser<EuropeanItem> {
override fun parse(item: EuropeanItem) {
println("parsing EuropeanItem")
}
}
fun main(args: Array<String>) {
val hashMap = HashMap<Class<out BaseItem>, ItemParser<*>>()
hashMap.put(AmericanItem::class.java, EuropeanItemParser())
hashMap.put(EuropeanItem::class.java, AmericanItemParser())
val inputItem = EuropeanItem()
val foundParser = hashMap[inputItem.javaClass]
foundParser?.parse(inputItem)
}
My issue is at the very last line, when I am trying to invoke the parser, I am getting the following compilation error
Out-projected type 'ItemParser<*>?' prohibits the use of 'public abstract fun parse(item: T): kotlin.Unit defined in ItemParser'
What am I doing wrong here ?
You have created a conflict between your declarations of the Map
and of the ItemParser
. The map can contain any descendant of BaseItem
but the ItemParser
is designed that each descendant only operates on one of the descendants of BaseItem
. So for a given instance of ItemParser
it must accept something it can recognize and here you can't do that because your foundParser
could be any descendant and not the one true expected type for that given ItemParser
instance. Which T
should it guess at?!? It cannot.
Therefore you have to design your API around the base class and not the descendants. You make it impossible for the compiler to know what is be passed to the parse()
method. The only one true thing you can know is that it is a BaseItem
instance.
Only you know the trick you are doing with the map that guarantees you are calling the correct instance with the correct type. The compiler has no idea of your logic that makes that a guarantee.
I would suggest you change your API to add an internalParse
method for which you cast do your work, wrapped by a generic parse
function that double checks and does the evil cast.
abstract class BaseItem
class AmericanItem : BaseItem()
class EuropeanItem : BaseItem()
interface ItemParser<T: BaseItem> {
@Suppress("UNCHECKED_CAST")
fun parse(item: BaseItem) {
val tempItem = item as? T
?: throw IllegalArgumentException("Invalid type ${item.javaClass.name} passed to this parser")
internalParse(tempItem)
}
fun internalParse(item: T)
}
class AmericanItemParser : ItemParser<AmericanItem> {
override fun internalParse(item: AmericanItem) {
println("AmericanItemParser")
}
}
class EuropeanItemParser : ItemParser<EuropeanItem> {
override fun internalParse(item: EuropeanItem) {
println("parsing EuropeanItem")
}
}
fun main(args: Array<String>) {
val hashMap = HashMap<Class<out BaseItem>, ItemParser<*>>()
hashMap.put(AmericanItem::class.java, EuropeanItemParser())
hashMap.put(EuropeanItem::class.java, AmericanItemParser())
val inputItem = EuropeanItem()
val foundParser = hashMap[inputItem.javaClass]
foundParser?.parse(inputItem)
}
Note you could also use the Kotlin class instead of the Java class which would be of type KClass<out T>
.
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