Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiating generic array in Kotlin

Tags:

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
   }
}
like image 926
Lancaster Avatar asked Jan 30 '17 16:01

Lancaster


People also ask

How do you get the generic parameter class in Kotlin?

There are no direct ways to do this in Kotlin. In order to check the generic type, we need to create an instance of the generic class<T> and then we can compare the same with our class.

What is out T in Kotlin?

"Out" keyword is extensively used in Kotlin generics. Its signature looks like this − List<out T> When a type parameter T of a class C is declared out, then C can safely be a super type of C<Derived>. That means, a Number type List can contain double, integer type list.

What is the difference between * and any in Kotlin generics?

When we define a collection with "*", it should contain the object of only that type. There should not be any mix and match between the data types inside a collection. If we use "Any", we can mix and match the data types, which means we can have multiple data types in a collection.


2 Answers

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.

like image 93
Ilya Avatar answered Sep 22 '22 11:09

Ilya


Personally, the best workaround for me was:

@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
like image 28
Riki137 Avatar answered Sep 24 '22 11:09

Riki137