Обобщение 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
, а второй параметр - длина массива.