Kotlin компилятор не может проверить обобщенный c тип класса - PullRequest
0 голосов
/ 12 марта 2020

TL; DR

Компилятор Kotlin выдает здесь ошибку (несоответствие типов):

fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() else // ...

С сигнатурой класса B: class B : A(), класс Util равен class Util<T: A>, а класс UtilB равен class UtilB: Util<B>().

Компилятор Kotlin выдает предупреждение (непроверенное приведение) здесь:

fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() as Util<T> else // ...

Насколько я понимаю Kotlin smart cast должен знать, что UtilB() as Util<T> проверяется t is B.

Java Код и компилятор дают точно такой же результат.

Насколько я знаю, что это должно быть ограничение на Java генериков. Как я могу это исправить?

Описание проблемы

У меня есть следующие настройки, где абстрактный класс имеет несколько реализаций и класс util, который обеспечивает одинаковые функциональные возможности для каждого из эти реализации.

Чтобы быть в безопасности типов, я решил создать абстрактный класс Util<T: A> и для каждого производного класса A другой UtilB: Util<B> класс.

Чтобы получить правильное использование класс для каждой реализации, я создал функцию для сопутствующего объекта getUtil, которая возвращает правильный класс утилит для каждой реализации на основе параметра универсального c типа T, который расширяет A: T: A, таким образом, возвращая тип Util<T> .

Однако, когда я написал тело функции для каждого производного класса A, проверив тип параметра с помощью is B, а затем вернул правильное значение util с помощью UtilB(), компилятор Kotlin дал мне ошибку в точке возврата, говоря, что UtilB не относится к типу Util<T>, хотя это должно быть.

Затем я произнесла UtilB Util<B> и это сработало, но дало мне ошибку "Unchecked cast". В соответствии с моим пониманием Kotlin умное приведение должно быть в состоянии выяснить, что оно действительно является проверенным приведением (проверено с помощью is B), и после выполнения быстрого теста оно также оказалось действительным ...

Я переписал тот же код в Java с точно такими же результатами ...

Насколько я знаю, это ограничение дженериков Java / Kotlin. Я хотел бы знать, как я могу проверить этот актерский состав. Это вообще возможно?

Код

Вот минимальный рабочий (или не работающий) пример:

abstract class A
class B : A()
class C : A()

abstract class Util<T : A> {
    abstract fun getName(): String
    companion object {
        fun <T : A> getUtil(t: T): Util<T> = when(t) {
            is B -> UtilB() as Util<T> // warning
            is C -> UtilC() // this event gives an error
            else -> throw IllegalArgumentException("No util for this class.")
        }
    }
}

class UtilB : Util<B>() {
    override fun getName(): String = "B"
}

class UtilC : Util<C>() {
    override fun getName(): String = "C"
}

fun main() {
    val b = B()
    val c = C()
    val utilB = Util.getUtil(b)
    val utilC = Util.getUtil(c)
    println(utilB.getName()) // prints B
    println(utilC.getName()) // prints C
}

Ответы [ 2 ]

0 голосов
/ 12 марта 2020

Насколько я понимаю Kotlin умный актер должен знать, что UtilB() as Util<T> проверен t is B.

Нет, не должен, потому что это неправильно. Совершенно законно вызывать

getUtil<A>(B())

, в этом случае t is B имеет значение true, но T равно A, а UtilB не расширяется Util<A>. Вы можете попытаться исправить это с помощью ковариации, но это тоже не сработает:

class D : B()

getUtil<D>(D())

Теперь UtilB также необходимо расширить Util<D>.

Возможное решение состоит в том, чтобы добавьте параметр F-ограниченного типа к A, но метод все еще должен быть членом, чтобы избежать приведений (Kotlin не уточняет параметры типа внутри ветви when, например, Scala делает):

abstract class A<T : A<T>> {
    abstract fun util(): Util<T>
}
class B : A<B>() {
    override fun util() = UtilB()
}
class C : A<C>() {
    override fun util() = UtilC()
}

abstract class Util<T : A<T>> {
    abstract fun getName(): String
}

class UtilB : Util<B>() {
    override fun getName(): String = "B"
}

class UtilC : Util<C>() {
    override fun getName(): String = "C"
}
0 голосов
/ 12 марта 2020

Вот модифицированная версия, которая работает:

abstract class A
class B : A()
class C : A()

abstract class Util<T : A> {
    abstract fun getName(): String
    companion object {
        fun <T : A> getUtil(t: T): Util<A> = when(t) {    // here return Util<A>
            is B -> UtilB()                               // No need cast
            is C -> UtilC()
            else -> throw IllegalArgumentException("No util for this class.")
        }
    }
}

class UtilB : Util<A>() {                   // Replace B by A
    override fun getName(): String = "B"
}

class UtilC : Util<A>() {                  // Replace C by A
    override fun getName(): String = "C"
}

fun main() {
    val b = B()
    val c = C()
    val utilB = Util.getUtil(b)
    val utilC = Util.getUtil(c)
    println(utilB.getName()) // prints B
    println(utilC.getName()) // prints C
}

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

Вот реализация, использующая запечатанные классы:

sealed class AA {
    class BB : AA()
    class CC : AA()
}
sealed class AAU {
    abstract fun getName():String

    class BBU : AAU() {
        override fun getName()= "BB"
    }

    class CCU : AAU(){
        override fun getName()= "CC"
    }
}

fun getU(aa: AA) =
    when(aa) {
        is AA.BB -> AAU.BBU()
        is AA.CC -> AAU.CCU()
    }

fun main() {
   val bb = AA.BB()
   val cc = AA.CC()
   val bbu = getU(bb)
   val ccu = getU(cc)
   println(bbu.getName())
   println(ccu.getName())
}

Так что здесь нет необходимости генерировать исключение.

Это может быть еще проще:

sealed class AA {
    class BB : AA()
    class CC : AA()
}

fun getName(aa: AA) =
        when(aa) {
            is AA.BB -> "BB"
            is AA.CC -> "CC"
        }

fun main() {
    val bb = AA.BB()
    val cc = AA.CC()
    println(getName(bb))
    println(getName(cc))
}

Поскольку я не знаю вашего контекста, последняя реализация, возможно, не является правильным подходом.

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