Kotlin 泛型 Array<T> 导致“不能将 T 用作具体类型参数。请改用类”但 List<T> 不会
Posted
技术标签:
【中文标题】Kotlin 泛型 Array<T> 导致“不能将 T 用作具体类型参数。请改用类”但 List<T> 不会【英文标题】:Kotlin generics Array<T> results in "Cannot use T as a reified type parameter. Use a class instead" but List<T> does not 【发布时间】:2022-01-17 02:36:17 【问题描述】:我有一个接口,其中包含 T 的数组(或列表)和一些元数据。
interface DataWithMetadata<T>
val someMetadata: Int
fun getData(): Array<T>
如果我编写接口的最简单实现,我会在emptyArray()
上得到一个编译错误:“Cannot use T as a reified type parameter. Use a class instead.”
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 开发人员偏爱数组。是这样吗,如果是,为什么?
【问题讨论】:
【参考方案1】:查看 kotlin stdlib (jvm) 中emptyArray()
的声明,我们注意到reified
类型参数:
public inline fun <reified @PureReifiable T> emptyArray(): Array<T>
reified
类型参数意味着你可以在编译时访问T
的类,并且可以像T::class
一样访问它。您可以在Kotlin reference 中阅读有关reified
类型参数的更多信息。由于Array<T>
编译为java T[]
,我们需要在编译时知道类型,因此需要reified
参数。如果你尝试编写一个没有 reified
关键字的 emptyArray() 函数,你会得到一个编译器错误:
fun <T> emptyArray() : Array<T> = Array(0, throw Exception() )
不能将 T 用作具体类型参数。改用类。
现在,我们来看看emptyList()
的实现:
public fun <T> emptyList(): List<T> = EmptyList
这个实现根本不需要参数T
。它只返回内部对象EmptyList
,它本身继承自List<Nothing>
。 kotlin 类型 Nothing
是 throw
关键字的返回类型,并且是永远不存在的值 (reference)。如果一个方法返回Nothing
,就相当于在那个地方抛出异常。所以我们可以在这里安全地使用Nothing
,因为每次我们调用EmptyList.get()
,编译器都知道这会返回一个异常。
额外问题:
来自 Java 和 C++,我习惯于使用 ArrayList
或 std::vector
更容易使用这些数组。我现在使用 kotlin 几个月了,在编写源代码时,我通常看不到数组和列表之间有很大的区别。两者都有大量有用的扩展函数,它们的行为方式相似。然而,Kotlin 编译器处理数组和列表的方式非常不同,因为 Java 互操作性对 Kotlin 团队非常重要。我通常更喜欢使用列表,这也是我在你的情况下推荐的。
【讨论】:
【参考方案2】:问题在于Array
的泛型类型必须在编译时 已知,这由此处的reified
类型参数指示,如声明中所示:
public inline fun <reified @PureReifiable T> emptyArray(): Array<T>
只能创建像Array<String>
或Array<Int>
这样的具体数组,但不能创建Array<T>
类型的数组。
在这个answer 中,您可以找到几种解决方法。
【讨论】:
【参考方案3】:最适合我的解决方法是:
@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
【讨论】:
【参考方案4】:我在尝试返回 T 时得到了Type parameter T cannot be called as function
。
private fun <T> getData(): T
return T()
见What is the proper way to create new instance of generic class in 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()
,所以,没有尝试。另见How to get class of generic type parameter in Kotlin。
【讨论】:
【参考方案5】:我在上面的解决方案中遇到了一些问题。这是我使用来自kotlin-reflect 的typeOf
得出的结论:
@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
得到的泛型类,第二个参数是数组的长度。
【讨论】:
以上是关于Kotlin 泛型 Array<T> 导致“不能将 T 用作具体类型参数。请改用类”但 List<T> 不会的主要内容,如果未能解决你的问题,请参考以下文章