Котлин внепроектного типа запрещает использование - PullRequest
0 голосов
/ 01 ноября 2018

Я недавно попробовал следующее в Kotlin. Идея состоит в том, что я получу в качестве входных данных Item (например, AmericanItem), который расширяет BaseItem. Я пытаюсь иметь разные парсеры для каждого из этих пунктов Вот пример кода

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

Моя проблема в самой последней строке, когда я пытаюсь вызвать синтаксический анализатор, я получаю следующую ошибку компиляции

Out-projected type 'ItemParser<*>?' prohibits the use of 'public abstract fun parse(item: T): kotlin.Unit defined in ItemParser'

Что я здесь не так делаю?

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

Вы создали конфликт между вашими объявлениями Map и ItemParser. Карта может содержать любого потомка BaseItem, но ItemParser предназначен для того, чтобы каждый потомок работал только на один потомков BaseItem. Таким образом, для данного экземпляра ItemParser он должен принять что-то, что он может распознать, и здесь вы не можете этого сделать, потому что ваш foundParser может быть любым потомком, а не единственным истинно ожидаемым типом для данного данного ItemParser экземпляра. Какой T он должен угадать?!? Не может.

Поэтому вы должны разрабатывать свой API на основе базового класса, а не потомков. Вы лишаете компилятор возможности узнать, что передается методу parse(). Единственная истинная вещь, которую вы можете знать, это то, что это BaseItem экземпляр.

Только вы знаете, что вы делаете с картой, которая гарантирует, что вы вызываете правильный экземпляр с правильным типом. Компилятор не имеет представления о вашей логике, которая делает это гарантией.

Я бы посоветовал вам изменить свой API, добавив internalParse метод, для которого вы выполняете приведение, выполняющий свою работу, обернутый общей функцией parse, которая дважды проверяет и выполняет приведение зла.

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

Обратите внимание, что вы также можете использовать класс Kotlin вместо класса Java, который будет иметь тип KClass<out T>.

0 голосов
/ 01 ноября 2018

ItemParser<*> - синтаксический анализатор неизвестного T, а не T. Поскольку этот T неизвестен, нет значения, которое вы можете безопасно передать функции parse, которая ожидает T.

Если вы уверены, что карта содержит ItemParser<T> для данного ключа Class<T>, вы можете написать следующую вспомогательную функцию, которая использует непроверенное приведение:

fun <T : BaseItem> Map<Class<out T>, ItemParser<*>>.findParserFor(item: T) =
    get(item.javaClass) as ItemParser<T>?

, а затем используйте его как:

val foundParser = hashMap.findParserFor(inputItem)
foundParser?.parse(inputItem)

Обратите внимание, что в вашем примере карта ассоциирует ключ AmericanItem::class.java с EuropeanItemParser и наоборот

hashMap.put(AmericanItem::class.java, EuropeanItemParser())
hashMap.put(EuropeanItem::class.java, AmericanItemParser())

Код не будет выполнен из-за исключения во время выполнения:

ClassCastException: EuropeanItem не может быть приведен к AmericanItem

Исключение произойдет, когда вы передадите EuropeanItem функции parse(), а не функции findParserFor. Это происходит потому, что возвращенный экземпляр ItemParser<EuropeanItem> на самом деле является AmericanItemParser. Это результат использования неконтролируемого приведения, предполагающего, что типы будут совпадать, когда на самом деле они не будут соответствовать, и именно поэтому это приведение называется «непроверенным».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...