1

I wanted to be able to define a method to clone an object that is the same type of itself. I define the interface requesting such, but the following does not compile or run.

interface Foo {
  fun <T: Foo> copy() : T
}

class Bar(private val v:Int) : Foo {
  override fun copy():Bar = Bar(v)
}

main() {
  val bar1 = Bar(1)
  val bar2 = bar1.copy()
}

If however I write the implementing class in Java, it will compile

class Bar implements Foo {
  private int v;
  public Bar(int v) {this.v = v;}

  public Bar copy() {
    return new Bar(v);
  }
}

I can rewrite the code like the following that compiles:

interface Foo<out Foo>{
  fun copy(): Foo
}

class Bar(private val v:Int) : Foo<Bar> {
  override fun copy(): Bar =  Bar(v)
}

However the following will fail with error: no type arguments expected for fun copy(): Foo val newF = f.copy()

fun <T: Foo> addFoo(
    foo: T,
    fooList: List<T>,
): MutableList<T> {
  val result: MutableList<T> = arrayListOf()

  for (f in fooList) {
    val newF = f.copy<T>()
    result.add(newF)
  }
  result.add(foo)
  return result
}

Is there a good solution to the problem?

lty2022
  • 11
  • 2

1 Answers1

0

The problem here is that Foo doesn't know the exact type of the implementing class, so has no way to specify that its method returns that same type.

Unfortunately, Kotlin doesn't have self types (see this discussion), as they would handle this situation perfectly.

However, you can get close enough by using what C++ calls the curiously-recurring template pattern. In Kotlin (and Java) you do this by defining Foo with a type parameter explicitly extending itself (including its own type parameter):

interface Foo<T : Foo<T>> {
    fun copy(): T
}

Then the implementing class can specify itself as the type argument:

class Bar(private val v: Int) : Foo<Bar> {
    override fun copy(): Bar = Bar(v)
}

And because T is now the correct type, everything else works out. (In fact, the : Bar is redundant there, because it already knows what the type must be.)

Your addFoo() method will then compile with only a couple of changes: give it the same type parameter <T: Foo<T>>, and remove the (now wrong, but unnecessary) type parameter when calling f.copy(). A quick test suggests it does exactly what you want (creates a list with clones of fooList followed by foo).

Since it's often useful for a superclass or interface to refer to the implementing class, this pattern crops up quite often.

BTW, your code is easier to test if Bar has its own toString() implementation, as you can then simply print the returned list. You could make it a data class, or you could write your own, e.g.:

    override fun toString() = "Bar($v)"
gidds
  • 16,558
  • 2
  • 19
  • 26