如何在 Kotlin 中创建一个填充了 null 的通用数组?

15

我尝试过这个,但是代码没有编译。

class GenericClass<T>() {
    private var arr : Array<T>? = null

    {
        arr = Array<T>(10, { null })
    }
}

1
我下面发布了一个答案,但问题的真正根源是你正在使用数组。在大多数情况下,最好使用集合来代替。数组是低级优化的手段,而数组列表在99%的情况下同样适用。 - Andrey Breslav
1
在编程中,当你说“没有编译”时显示错误信息总是具有信息量且有用的,即使你认为这个错误是不言自明的。它可以提高问题的质量并使错误能够被检索到。在带有错误的行中作为注释放置错误信息是一个好的做法。 - Jayson Minard
4个回答

19

这段代码报告了两个编译器错误:一个是关于可为空类型,另一个是关于泛型。

可为空类型。Kotlin强制使用可为空引用类型的规则,在T可能被实例化为String的情况下,让arr成为Array类型,这样编译器不允许您将null放入此数组中。如果您需要null值,则必须将类型更改为Array:

class GenericClass<T>() {
    private var arr : Array<T?>? = null

    {
        arr = Array(10, { null }) // No need to specify type arguments again
    }
}

泛型。上述示例仍存在编译时错误,因为我们正在尝试构造一个未知类型 T 的数组。请注意,这个问题在Java中也存在。 Kotlin编译成JVM字节码涉及两件事:

  • 泛型类型参数在运行时被擦除,
  • 除了数组的泛型参数。

这意味着在字节码中,Kotlin必须创建某些具体类型的数组,而不是未知类型T的数组。每当它看到Array时,它可以创建对象数组,但是在这种情况下,这不起作用,例如:

fun test() {
    fun foo(srts: Array<String?>) {
        // ...
    }
    val gc = GenericClass<String>()
    foo(gc.arr)
}

在这里,我们试图传递 Object[],但需要的是 String[],所以会出现运行时错误。

这就是为什么 Kotlin 拒绝创建 T 类型的数组。您可以通过明确抑制类型系统来解决此问题,即使用类型转换:

class GenericClass<T>() {
    val arr : Array<T?>

    {
        arr = Array<Any?>(10, { null }) as Array<T?>
    }
}

在这里,我们明确请求创建一个任意类型的数组(编译为Object[]),然后将其强制转换为T类型的数组。编译器会发出警告,但会遵守我们的意愿。

请注意,上述有问题的示例仍然存在,即如果您将以这种方式创建的数组传递到期望字符串数组的位置,则运行时会失败。


3
创建包含空值的数组,您可以使用arrayOfNulls - bashor
1
@bashor,你不能这样做。arrayOfNulls是具体化的,因此无法转换为泛型。 - Poweranimal

2

方法

val array : Array<T?> = kotlin.arrayOfNulls<T>(size)

从文档中

/**
*Returns an array of objects of the given type with the given [size],
*initialized with null values.
*/
public fun <reified @PureReifiable T> arrayOfNulls(size: Int): Array<T?>

1
这个不行。arrayOfNulls 是具体化的,因此无法转换为泛型。 - Poweranimal

1
如果您需要在构造函数中初始化数组,可以添加一个内联工厂方法并使用 reified T 进行参数化。这个解决方案受到了答案 https://dev59.com/wVgQ5IYBdhLWcg3w-oxw#41946516 的启发。
class GenericClass<T> protected constructor(
    private val arr : Array<T?>
) {
    companion object {
        inline fun <reified T>create(size: Int) = GenericClass<T>(arrayOfNulls(size))
    }
}

fun main() {
    val strs = GenericClass.create<String>(10)
    ...
}

注意构造函数是受保护的,因为内联函数无法访问私有构造函数。
如果您需要在创建对象后创建数组,则可以将创建数组的 lambda 传递到方法中。Lambda 可以在扩展函数内部创建,因此数组类型的信息得以保留。@PublishedApi 注释用于封装私有方法 fill。
import GenericClass.Companion.fill

class GenericClass<T> {
    private var arr : Array<T?>? = null

    fun show() {
        print(arr?.contentToString())
    }

    private fun fill(arrayFactory: (size: Int) -> Array<T?>) {
        this.arr = arrayFactory(10)
    }

    @PublishedApi
    internal fun `access$fill`(arrayFactory: (size: Int) -> Array<T?>) = fill(arrayFactory)

    companion object {
        inline fun <reified T>GenericClass<T>.fill() {
            `access$fill`(arrayFactory = { size -> arrayOfNulls(size) })
        }
    }
}

fun main() {
    val strs = GenericClass<String>()
    strs.fill()
    strs.show()
}

0
您可以使用以下的辅助函数:
@Suppress("UNCHECKED_CAST")
fun <T> genericArrayOfNulls(size: Int): Array<T?> {
    return arrayOfNulls<Any?>(size) as Array<T?>
}

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