Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a generic array filled with nulls in Kotlin?

Tags:

kotlin

I tried this, and the code didn't compile.

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

    {
        arr = Array<T>(10, { null })
    }
}
like image 416
x2bool Avatar asked Nov 29 '13 11:11

x2bool


3 Answers

method

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

from docs

/**
*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?>
like image 170
TarikW Avatar answered Nov 19 '22 06:11

TarikW


There are two compiler errors reported in this code: one is about nullable types and another about generics.

Nullable types. Kotlin enforces a discipline of nullable references, and since T may be instantiated with, say, String making arr be of type Array, the compiler does not allow you to put nulls into this array. If you want nulls, you have to change the type to Array:

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

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

Generics. The example above still has a compile-time error, because we are trying to construct an array of an unknown type T. Note that this problem exists in Java as well. Kotlin being compiled to JVM byte code entails two things:

  • generics type arguments are erased at runtime,
  • except for generic arguments of arrays.

This means that in the byte code Kotlin has to create an array of some concrete type, and not an unknown type T. It could create arrays of Objects whenever it sees Array, but this would not work, for example, in this case:

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

Here, in the last line, we are trying to pass Object[] where String[] is expected, and get a runtime error.

This is why Kotlin refuses to create arrays of T. You can work around this problem by explicitly suppressing the type system, i.e. by using type casts:

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

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

Here we explicitly request creation of an array of Any (compiled to Object[]), and then type-cast it to an array of T. The compiler issues a warning, but obeys our will.

Note that the problematic example above remains, i.e. if you pass the array created this way where an array of strings is expected, it ill fail at run time.

like image 30
Andrey Breslav Avatar answered Nov 19 '22 04:11

Andrey Breslav


If you need to initialize array in the constructor, you can add an inline factory method and parametrize it using reified T. This solution is inspired by answer https://stackoverflow.com/a/41946516/13044086

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

Notice that the constructor is protected, because inline function can't access a private constructor.

If you need to create an array after the object is created, you can pass lambda that creates the array into the method. Lambda can be created inside of extension function, so information about type of the array is preserved. @PublishedApi annotation is used to encapsulate private method 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()
}
like image 1
fdermishin Avatar answered Nov 19 '22 04:11

fdermishin