关于java: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

我有一个接口,其中包含T的数组(或列表)和一些元数据。

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

如果我编写接口的最简单的实现,则会在emptyArray()上收到编译错误:"不能将T用作化类型参数。请改用类。"

1
2
3
4
5
6
7
8
9
10
11
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
    }
}

但是,如果我同时将接口和实现都更改为列表,则不会有编译时问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。是这种情况,如果是,为什么?


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

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

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

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

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


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

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

此实现完全不需要参数T。它仅返回内部对象EmptyList,该对象本身是从List<Nothing>继承的。 Kotlin类型Nothingthrow关键字的返回类型,并且是一个永不存在的值(引用)。如果方法返回Nothing,则等效于在该位置引发异常。因此我们可以在这里安全地使用Nothing,因为每次调用EmptyList.get()时,编译器都知道这将返回异常。


奖金问题:

来自Java和C,我习惯使用ArrayListstd::vector来轻松使用该数组。我现在使用kotlin已有几个月,在编写源代码时,我通常不会在数组和列表之间看到很大的差异。两者都有大量有用的扩展功能,它们的行为类似。但是,由于Java互操作性对于Kotlin团队非常重要,因此Kotlin编译器处理数组和列出的列表非常不同。我通常更喜欢使用列表,这也是我建议您使用的列表。


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

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

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

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


尝试返回T时得到Type parameter T cannot be called as function

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

请参阅在kotlin中创建泛型类的新实例的正确方法是什么?:

1
2
3
4
5
6
7
8
9
10
private fun < T > create(
    method: (Int) -> T,
    value: Int
): T {
    return method(value) // Creates T(value).
}

// Usage:

create(::ClassName, 1)

其中ClassName扩展T

也许这也会有所帮助:

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

但是在我的情况下,我必须返回T(parameter),而不是T(),因此没有尝试。另请参见如何在Kotlin中获取通用类型参数的类。


最适合我的解决方法是:

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