Kotlin泛型Array <t>导致“不能使用T作为一个改进的类型参数。请改用类”但List <t>不

时间:2017-10-22 00:20:19

标签: java arrays generics kotlin reification

我有一个包含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。是这样的,如果是这样,为什么?

4 个答案:

答案 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类型Nothingthrow关键字的返回类型,是永不存在的值reference)。如果方法返回Nothing,则相当于在该位置抛出异常。所以我们可以在这里安全地使用Nothing,因为每当我们调用EmptyList.get()时,编译器都会知道这将返回异常。

加分问题:

来自Java和C ++,我习惯ArrayListstd::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