Instantiating generic array in Kotlin
Asked Answered
W

2

25

Why this doesn't compile? I get compile error in 3 line

Cannot use T as reified type parameter. Use class instead

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

Solution

I added a factory function which looks like a second constructor but implemented in inline function

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
   }
}
Wraf answered 30/1, 2017 at 16:44 Comment(1)
Possible duplicate of Kotlin NDArray with a lambda constructor with generic return typePettus
L
27

JVM arrays, on which Kotlin arrays are mapped to, require the element type to be known at compile time to create an instance of array.

So you can instantiate Array<String> or Array<Any>, but not Array<T> where T is a type parameter, representing the type that is erased at compile time and hence is unknown. To specify that a type parameter must be known at compile time it is marked with reified modifier.

There are several options, what you can do in this situation:

  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.

Lunate answered 30/1, 2017 at 22:27 Comment(2)
Thanks for explanation, I like the second way. I updated my post and code works properly, but now I have a public constructor in Matrix2d class which can be used by all, and it's confusing, because I have a third parameter as an array. And if I use that constructor I should pass the number of rows and columns. I wanted to specify contructor scope to private, but it's not available for inline function. How can I solve this problem in a different way?Wraf
Use internal constructor, though to call it from public inline function, you have to annotate it with @PublishedApi annotation (available in Kotlin 1.1). I'll update the example.Lunate
M
4

Personally, the best workaround for me was:

@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
Multiplicate answered 12/5, 2018 at 8:38 Comment(2)
This solution is just as viable as solution 3 in the selected answer; just probably should have private access.Lanai
I don't think this works, it throws a ClassCastException with a misleading error message (see #54537395) but the fact still remains that it cannot do the castMixon

© 2022 - 2024 — McMap. All rights reserved.