Kotlin 泛型 Array<T> 结果为 "Cannot use T as a reified type parameter. Use a class instead" 但 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

我有一个包含 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 而不是 List 的唯一原因是我经常看到 Kotlin 开发人员偏爱 Arrays。是这样吗,如果是,为什么?

查看kotlin stdlib(jvm)中emptyArray()的声明,我们注意到reified类型参数:

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

reified 类型参数意味着你可以在编译时访问 T 的 class 并且可以像 T::class 一样访问它。您可以在 Kotlin reference 中阅读有关 reified 类型参数的更多信息。由于 Array<T> 编译为 java T[],我们需要在编译时知道类型,因此需要 reified 参数。如果您尝试编写不带 reified 关键字的 emptyArray() 函数,您将收到编译器错误:

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

Cannot use T as a reified type parameter. Use a class instead.


现在,让我们看一下emptyList()的实现:

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

此实现根本不需要参数 T。它只是 return 内部对象 EmptyList,它本身继承自 List<Nothing>。 kotlin 类型 Nothingthrow 关键字的 return 类型,并且是 永远不存在的值 (reference)。如果一个方法returns Nothing,就相当于在那个地方抛出异常。所以我们可以在这里安全地使用 Nothing 因为每次我们调用 EmptyList.get() 编译器都知道这将 return 一个异常。


加分题:

来自 Java 和 C++,我习惯了 ArrayListstd::vector 以便更容易使用该数组。我现在使用 kotlin 几个月了,在编写源代码时,我通常看不出数组和列表之间有什么大的区别。两者都有大量有用的扩展功能,它们的行为方式相似。然而,Kotlin 编译器处理数组和列表的方式非常不同,因为 Java 互操作性对于 Kotlin 团队来说非常重要。我通常更喜欢使用列表,这也是我在你的情况下推荐的。

问题是 Array 的泛型类型必须在 编译时 已知,这由此处的 reified 类型参数指示,如声明中所示:

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

只能创建像 Array<String>Array<Int> 这样的具体数组,但不能创建 Array<T>.

类型的数组

在此 中,您可以找到几种解决方法。

最适合我的解决方法是:

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

我在尝试 return 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()
}

但在我的情况下,我不得不 return T(parameter),而不是 T(),所以,没有尝试。另见 .

我在上面的解决方案中遇到了一些问题。这是我使用 typeOf from kotlin-reflect:

想出的
@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得到的泛型的class,第二个参数是数组的长度。