Generic Object Creation

What would be the Kotlin way to create objects of an unkown type. Given the following code:

interface Cell { ... }

class SomeCell : Cell { ... }

class CellList<T: Cell>(val num: Int) {
    private val data: MutableList<T> = ArrayList(num)

    init {
        for (i in 0..num - 1) {
            data[i] = ???  // how can I create an instance here?
        }
    }
}

I’d like to instantiate a number of CellLists that differ in their type of cell. How can I idiomatically achieve this in Kotlin?

You need to pass a KClass<out Cell> as a constructor parameter to CellList, and to invoke the constructor using reflection. You can also create a factory function with a reified type parameter to enable creating CellList instances with a somewhat cleaner syntax.

With Kotlin I prefer to pass along a factory function. This because it also gives you the freedom to use anything other then default constructors. Thus:

fun main(args: Array<String>) {
    val cl = CellList(10) {
        SomeCell()
    }
}

interface Cell { }

class SomeCell : Cell { }

class CellList<T: Cell>(val num: Int, factory : (Int) -> T) {
    private val cells: MutableList<T> = java.util.ArrayList(num)

    init {
        for (i in 0..num - 1) {
            cells.add(factory(i))
        }
    }
}
2 Likes

I really want to avoid the reflection route. Can you explain what you mean with your second suggestion?

Sometimes it would be really nice, if interfaces could enforce certain constructors as well.

You can use inline functions with reified type parameters to achieve something that closest to your initial question. You can go as berserk as:

fun main(args: Array<String>) {
    CellList(10) {
        SomeCell()
    }
    
    CellList<SomeCell>(10)
}

interface Cell { }

class SomeCell : Cell { }

class CellList<T: Cell>(val num: Int, factory : (Int) -> T) {
    companion object {
        inline operator fun <reified T: Cell> invoke(num : Int) = CellList(num) {
            T::class.java.newInstance()
        }
    }
    private val cells: MutableList<T> = java.util.ArrayList(num)
    
    init {
        for (i in 0..num - 1) {
            cells.add(factory(i))
        }
    }
}
1 Like

Thanks.

This is super cool and does exactly what I want. Actually, it opens up a couple of new options. However, I’m having trouble finding the place where this is documented.

As I understand it, there’s some compiler magic going on that transforms CellList<SomeCell>(10) into CellList.invoke<SomeCell>(10).

That way you can add useful “constructors” to String:

operator fun String.Companion.invoke(repeatingChars: String, num: Int): String {
    val builder = StringBuilder()
    for (i in 0..num - 1) {
        builder.append(repeatingChars)
    }
    return builder.toString()
}

Unfortunately, that is not possible for Java classes that do not declare a companion object (like BigInteger). I’m wondering if that would be nice to be able to externally add those companion objects as well?

Something like

companion object BigInteger.Companion {
    ...
}

There’s no magic here, just a “simple” operator overloading combined with reified inline functions and companion objects :wink:

Everything is in the docs: https://kotlinlang.org/docs/reference/operator-overloading.html

I second that idea. I’d like to be able to write extension functions for such objects (no companion objects need to be created in this case, it’s just about the syntax).

1 Like

How can I do this in Kotlin multiplatform?

To be honest, I believe that this is a clear violation of the invoke operator contract and I think it should not be used this way. Also, it is hard to understand when you read the code. It can be used in a strange way:

CellList.invoke<SomeCell>(10)

Function reference is totally different than for a constructor:

val f: ()->Tree = CellList.Companion::invoke

I suggest using fake constructor instead:

class CellList<T: Cell>(val num: Int, factory : (Int) -> T) {
    private val cells: MutableList<T> = MutableList(num) { factory(it) }
}

inline fun <reified T: Cell> CellList(num : Int) = CellList(num) {
    T::class.java.newInstance()
}

Use is the same:

fun main() {
    val list = CellList<SomeCell>(10)
}

The notation is till not obvious, but it is much easier to understand because it requires only understanding that this is a function, instead of the understanding concept of operator overloading, what is invoke operator and companion object. Reflection is the same as to the primary constructor:

val f = ::CellList

We can place it to any package or module so we can easily manipulate the visibility of this function.

Also using it in the common modules is easy :stuck_out_tongue:

If you have idiomatic Kotlin I would agree with you. There is however one case where using the invoke operator on the companion works rather well (in combination with @JvmName and that is if you want to have a factory method that works well in a Java context. Java clients often have Type.create or similar as their factory. This can be done with the invoke operator if it is renamed on Java.