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