Обобщение Kotlin Array<T> приводит к "Невозможно использовать T в качестве параметра типа reified. Вместо этого используйте класс", но List<T> не делает

У меня есть интерфейс, который содержит массив (или список) T и некоторые метаданные.

interface DataWithMetadata<T> {
    val someMetadata: Int
    fun getData(): Array<T>
}

Если я напишу простейшую реализацию интерфейса, я получу ошибку компиляции на emptyArray(): "Невозможно использовать T в качестве параметра типа reified. Вместо этого используйте класс."

class ArrayWithMetadata<T>(override val someMetadata: Int): DataWithMetadata<T> {
    private var myData: Array<T> = emptyArray()

    override fun getData(): Array<T> {
        return myData
    }

    fun addData(moreData: Array<T>) {
        this.myData += moreData
    }
}

Однако если я изменю и интерфейс, и реализацию на список, у меня не возникнет проблем во время компиляции:

interface DataWithMetadata<T> {
    val someMetadata: Int
    fun getData(): List<T>
}

class ListWithMetadata<T>(override val someMetadata: Int): DataWithMetadata<T> {
    private var myData: List<T> = emptyList()

    override fun getData(): List<T> {
        return myData
    }

    fun addData(moreData: Array<T>) {
        this.myData += moreData
    }
}

Я подозреваю, что в моем выпуске есть интересный урок по дженерикам Kotlin. Может кто-нибудь сказать мне, что компилятор делает под капотом и почему Array дает сбой, а List - нет? Есть ли идиоматический способ заставить реализацию Array компилироваться в этом контексте?

Дополнительный вопрос: единственная причина, по которой я обратился к Array over List, заключается в том, что я часто вижу, что разработчики Kotlin предпочитают Arrays. Так ли это, и если да, то почему?

6 ответов

Решение

Глядя на декларацию emptyArray() в kotlin stdlib (jvm) мы замечаем reified параметр типа:

public inline fun <reified @PureReifiable T> emptyArray(): Array<T>

reified Параметр type означает, что у вас есть доступ к классу T во время компиляции и может получить к нему доступ, как T::class, Вы можете прочитать больше о reified параметры типа в справочнике Kotlin. поскольку Array<T> компилируется в Java T[]нам нужно знать тип во время компиляции, следовательно, reified параметр. Если вы попытаетесь написать функцию emptyArray() без reified ключевое слово, вы получите ошибку компилятора:

fun <T> emptyArray() : Array<T> = Array(0, { throw Exception() })

Невозможно использовать T в качестве параметра типа reified. Используйте класс вместо этого.


Теперь давайте посмотрим на реализацию emptyList():

public fun <T> emptyList(): List<T> = EmptyList

Эта реализация не нуждается в параметре T совсем. Он просто возвращает внутренний объект EmptyList, который сам наследует от List<Nothing>, Тип котлин Nothing это тип возвращаемого значения throw ключевое слово и это значение, которое никогда не существует ( ссылка). Если метод возвращает Nothing, эквивалентно выбрасыванию исключения в этом месте. Так что мы можем безопасно использовать Nothing здесь, потому что каждый раз, когда мы будем называть EmptyList.get() Компилятор знает, что это вернет исключение.


Бонусный вопрос:

Исходя из Java и C++, я привык ArrayList или же std::vector гораздо проще использовать эти массивы. Сейчас я использую kotlin в течение нескольких месяцев и обычно не вижу большой разницы между массивами и списками при написании исходного кода. Оба имеют множество полезных функций расширения, которые ведут себя одинаково. Однако компилятор Kotlin обрабатывает массивы и списки очень по-разному, поскольку совместимость Java очень важна для команды Kotlin. Я обычно предпочитаю использовать списки, и это то, что я бы рекомендовал и в вашем случае.

Проблема в том, что общий тип Array должен быть известен во время компиляции, что указано reified введите здесь параметр, как показано в объявлении:

public inline fun <reified @PureReifiable T> emptyArray(): Array<T>

Можно создавать только конкретные массивы, такие как Array<String> или же Array<Int> но не типа Array<T>,

В этом ответе вы можете найти несколько обходных путей. Надеюсь, вы найдете подходящий способ.

Я получил Type parameter T cannot be called as function при попытке вернуть Т.

private fun <T> getData(): T {
    return T()
}

См. Как правильно создать новый экземпляр универсального класса в kotlin?:

private fun <T> create(
    method: (Int) -> T,
    value: Int
): T {
    return method(value) // Creates T(value).
}

// Usage:

create(::ClassName, 1)

где ClassName расширяет T.

Также, возможно, это поможет:

private inline fun <reified T> getData(): T {
    return T::class.java.newInstance()
}

Но в моем случае мне пришлось вернуться T(parameter)не T()Итак, не пробовал. См. Также Как получить класс параметра универсального типа в Kotlin.

Обходной путь, который работал лучше всего для меня, был:

@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>

У меня были проблемы с решением выше. Вот что я придумал, используяиз котлина-рефлекс :

      @Suppress("UNCHECKED_CAST")
private inline fun <reified T> createArrayOfGeneric(): Array<T> {
    return java.lang.reflect.Array.newInstance(typeOf<T>().javaType as Class<*>, 10) as Array<T>
}

В newInstance метод принимает тип java как класс универсального, который вы получаете от typeOf, а второй параметр - длина массива.

Я изменил это сArray<T>кArrayList<T>и это сработало.

Другие вопросы по тегам