Kotlin 泛型 Array<T> 导致 "无法使用 T 作为具体化的类型参数。请改用类",但 List<T> 没有这个问题。

68

我有一个包含元素类型为 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而不是List的唯一原因是我经常看到Kotlin开发人员喜欢使用Arrays。是这样吗?如果是的话,为什么?

6个回答

51

查看 Kotlin 标准库(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类型Nothingthrow关键字的返回类型,是一个从未存在的值(reference)。如果一个方法返回Nothing,那么在那个位置抛出异常是等效的。因此,我们可以安全地在这里使用Nothing,因为每次我们调用EmptyList.get()时,编译器都知道它会返回异常。


奖励问题:

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


17

问题在于必须在编译时知道Array的通用类型,这由此处的reified类型参数表示,如声明中所示:

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

只能创建类似 Array<String>Array<Int> 的具体数组,而不能创建类型为 Array<T> 的数组。

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


4

当我尝试返回T时,出现了类型参数T不能被调用为函数的错误。

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中获取泛型类型参数的类


1

我在上面的解决方案中遇到了一些问题。这是我使用 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方法接受从typeOf获取的泛型的Java类型作为类,并且第二个参数是数组的长度。


1
我最好的解决方法是:

对我而言,最好的解决方法是:

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

-1

我将它从Array<T>改为ArrayList<T>,然后它就可以工作了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接