我有一个包含T和一些元数据的数组(或列表)的接口。
interface DataWithMetadata<T> {
val someMetadata: Int
fun getData(): Array<T>
}
如果我编写最简单的接口实现,我会在emptyArray()
上遇到编译错误:“不能使用T作为一个改进的类型参数。请改用类。”
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。是这样的,如果是这样,为什么?
答案 0 :(得分:18)
查看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团队非常重要。我通常更喜欢使用列表,这也是我在你的情况下推荐的。
答案 1 :(得分:6)
问题是Array
的泛型类型必须在编译时知道,这由reified
类型参数在此表示,如声明中所示:
public inline fun <reified @PureReifiable T> emptyArray(): Array<T>
只能创建像Array<String>
或Array<Int>
这样的具体数组,但不能创建Array<T>
类型的具体数组。
在此answer中,您可以找到几种解决方法。希望你找到合适的方式。
答案 2 :(得分:0)
对我来说最有效的解决方法是:
@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
答案 3 :(得分:0)
当我尝试返回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。