在 Kotlin 中实例化泛型数组

25

为什么这个代码无法编译?我在第三行得到了编译错误。

无法使用T作为具体化的类型参数。请使用类代替。

class Matrix2d<T>(val rows: Int, val cols: Int, init: (Int, Int) -> T) {

   var data = Array(rows * cols, { i ->
      val r = Math.floor(i.toDouble() / cols).toInt()
      init(r, i - r * cols)
   })

   operator fun get(row: Int, col: Int): T = data[row * cols + col]

   operator fun set(row: Int, col: Int, v: T) = {
      data[row * cols + col] = v
   }
}

解决方案

我添加了一个工厂函数,它看起来像第二个构造函数,但是实现为内联函数。

class Matrix2d<T>(val rows: Int, val cols: Int, private val data: Array<T>) {

   companion object {
      operator inline fun <reified T> invoke(rows: Int, cols: Int, init: (Int, Int) -> T): Matrix2d<T> {
         return Matrix2d(rows, cols, Array(rows * cols, { i ->
            val r = Math.floor(i.toDouble() / cols).toInt()
            init(r, i - r * cols)
         }))
      }
   }

   init {
      if (rows * cols != data.size) throw IllegalArgumentException("Illegal array size: ${data.size}")
   }

   operator fun get(row: Int, col: Int): T = data[row * cols + col]

   operator fun set(row: Int, col: Int, v: T) {
      data[row * cols + col] = v
   }
}

2个回答

27

JVM数组是Kotlin数组映射到的对象,需要在编译时知道元素类型才能创建数组实例。

因此,您可以实例化Array<String>Array<Any>,但不能实例化Array<T>,其中T是一个类型参数,表示在编译时被擦除并且未知的类型。 为了指定类型参数必须在编译时知道,它被标记为reified修饰符。

在这种情况下,您有几个选项:

  1. Use MutableList<T> for storing elements, which doesn't require reified T:

    // MutableList function, available in Kotlin 1.1
    val data = MutableList(rows * cols, { i ->
       val r = i / cols
       init(r, i % cols)
    })
    // or in Kotlin 1.0
    val data = mutableListOf<T>().apply {
        repeat(rows * cols) { i ->
            val r = i / cols
            add(init(r, i % cols))
        }
    }
    
  2. Create an array from an inline function with reified type parameter:

    inline fun <reified T> Matrix2d(val rows: Int, val cols: Int, init: (Int, Int) -> T) = 
        Matrix2d(rows, cols, Array(rows * cols, { .... })
    
    class Matrix2d<T> 
        @PublishedApi internal constructor(
            val rows: Int, val cols: Int,
            private val data: Array<T>
        ) 
    
  3. Use Array<Any?> as the storage, and cast its values to T in get function:

    val data = Array<Any?>(rows * cols, { .... })
    
    operator fun get(row: Int, col: Int): T = data[row * cols + col] as T
    
  4. Pass a parameter of type Class<T> or KClass<T> to constructor and use java reflection to create an instance of array.


感谢您的解释,我喜欢第二种方式。我更新了我的帖子和代码,现在Matrix2d类中有一个公共构造函数,可以被所有人使用,这很令人困惑,因为我有一个作为数组的第三个参数。如果我使用该构造函数,我应该传递行数和列数。我想将构造函数范围指定为私有,但它对于内联函数不可用。我该如何以不同的方式解决这个问题? - Lancaster
1
使用内部构造函数,但是要从公共内联函数调用它,您必须使用@PublishedApi注释进行注释(在Kotlin 1.1中可用)。我会更新示例。 - Ilya
有没有办法使用原始数组来实现这个?我想创建一个通用的“Series”类,它包含一个私有的原始数组,如“long[]”或“double[]”。 - Zelazny7

3

就我个人而言,最好的解决办法是:

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

这个解决方案与所选答案中的解决方案3一样可行;只是可能应该具有私有访问权限。 - Bryan W. Wagner
我认为这个不起作用,它会抛出一个具有误导性错误消息的ClassCastException(请参见https://dev59.com/01QJ5IYBdhLWcg3wHSHK),但事实仍然是它无法进行转换。 - Emanuel Moecklin

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