55

I have an interface that contains an array (or list) of T and some metadata.

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

If I write the simplest implementation of the interface, I get a compile error on the 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
    }
}

However, if I change both the interface and the implementation to a list, I have no compile-time issues:

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
    }
}

I suspect there is some interesting lesson in Kotlin generics inside my issue. Can anyone tell me what the compiler is doing under the hood and why Array fails but List does not? Is there an idiomatic way to make the Array implementation compile in this context?

Bonus question: The only reason I reached for Array over List is that I often see Kotlin developers favor Arrays. Is this the case, and if so, why?

4

5 回答 5

44

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

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

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

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, is 相当于在那个地方抛出一个异常。所以我们可以安全地Nothing在这里使用,因为每次我们调用EmptyList.get()编译器都知道这会返回一个异常。


奖金问题:

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

于 2017-10-22T04:38:28.887 回答
13

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

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

只能创建像Array<String>orArray<Int>但不是 type 的具体数组Array<T>

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

于 2017-10-22T12:59:16.637 回答
2

最适合我的解决方法是:

@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
于 2018-05-12T08:40:34.077 回答
2

当我Type parameter T cannot be called as function试图返回 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 中获取泛型类型参数的类

于 2020-03-26T16:15:18.457 回答
1

我在上面的解决方案中遇到了一些麻烦。这是我typeOfkotlin-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第二个参数是数组的长度。

于 2021-10-21T18:45:22.470 回答