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?
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()
}
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>
}