genericsinterfacecopykotlinself-type

Write a Copyable interface more elegant than in Java


I'm trying to write an interface that classes can implement to make them "copyable", a (type)safe Clonable.

In Java, I would do something like this, using recursive generics:

 public interface Copyable<C extends Copyable<C>> {
      C copy();
 }

 public class Example implements Copyable<Example> {
      ...

      @Override
      public Example copy()
      {
           return new Example(this); //invoke copy constructor
      }
 }

Obviously this is not that elegant, both the headers of Copyable and Example look overcomplicated. Is there a more elegant way to achieve this in Kotlin?


Solution

  • Here's an attempt to reduce the generic boilerplate by sacrificing some static type safety:

    interface Copyable {
        fun createCopy(): Copyable
    }
    
    inline fun <reified T : Copyable> T.copy(): T = createCopy() as T
    

    We can leverage extension functions to get the generic type of the receiver without recursive generics. We make the extension function inline to reify the type parameter so that the cast will be checked and throws an exception if the implementing class doesn't return an instance of the same type.

    Here's an example usage

    class Example(val a: String) : Copyable {
        constructor(e: Example) : this(e.a)
    
        override fun createCopy() = Example(this)
    }
    
    
    fun main(args: Array<String>) {
        val copiedExample: Example = Example("a").copy()
    }
    

    Alternative solution using covariance

    Depending on your use case, you don't even need the copy method to be generic as we can leverage covariance. Declare your types like so

    interface Copyable {
        fun copy(): Copyable
    }
    
    class Example(val a: String) : Copyable {
        constructor(f: Example) : this(f.a)
    
        override fun copy() = Example(this)
    }
    

    And as you can see, the code val copiedExample: Example = Example("a").copy() still compiles. This is because overriden methods can return a more specific type than the super method and using Kotlin's single expression functions the type we want is automatically inferred.

    This can however lead to problems if you're not working with the specific type directly, but say a subinterface of Copyable. The following code does not compile:

    interface CopyableIterable<T> : Iterable<T>, Copyable
    
    class Example : CopyableIterable<String> {
    
        constructor(e: Example)
    
        override fun copy() = Example(this)
        override fun iterator() = TODO()
    }
    
    fun foo(ci: CopyableIterable<String>) {
        val copy: CopyableIterable<String> = ci.copy() // error: type mismatch
    }
    

    The fix for this is simple, override the copy method in the subinterface as well:

    interface CopyableIterable<T> : Iterable<T>, Copyable {
        override fun copy(): CopyableIterable<T>
    }