Suppose I've got a sealed class
hierarchy like that:
sealed class A {
abstract val x: Int
abstract fun copyX(x1: Int): A
}
data class A1(override val x: Int, val s1: String) : A() {
override fun copyX(x1: Int): A {
return this.copy(x = x1)
}
}
data class A2(override val x: Int, val s2: String) : A() {
override fun copyX(x1: Int): A {
return this.copy(x = x1)
}
}
All the data classes have field x
and should provide method copyX(x1: Int)
to copy all the fields but x
and override x
with x1
. For instance,
fun foo(a: A): A { a.copyX(100) }
The definitions above probably work but the repeating copyX
across all the data classes seem very clumsy. How would you suggest get rid of this repeated copyX
?
2.1. One of the advantages of sealed interfaces over sealed classes is the ability to inherit from multiple sealed interfaces. This is impossible for sealed classes because of the lack of multiple inheritance in Kotlin.
The main purpose of a sealed class is to take away the inheritance feature from the class users so they cannot derive a class from it. One of the best usage of sealed classes is when you have a class with static members. For example, the Pens and Brushes classes of the System.
This means the heir of the sealed class can have as many as any number of instances and can store states, but the enum class cannot. Sealed classes also offer a restricted number of hierarchies. This means if you have a different class defined in another file in your project, you cannot extend the class Menu .
Sealed classes cannot be instantiated directly. Sealed classes cannot have public constructors (The constructors are private by default). Sealed classes can have subclasses, but they must either be in the same file or nested inside of the sealed class declaration.
First, you can implement copyX
as an extension (or even A
's member) so as to concentrate the code in one place and avoid at least duplicating the copyX
function in the sealed class subtypes:
sealed class A {
abstract val x: Int
}
fun A.copyX(x1: Int): A = when (this) {
is A1 -> copy(x = x1)
is A2 -> copy(x = x1)
}
data class A1(override val x: Int, val s1: String) : A()
data class A2(override val x: Int, val s2: String) : A()
If you have a lot of sealed subtypes and all of them are data
classes or have a copy
function, you could also copy them generically with reflection. For that, you would need to get the primaryConstructor
or the function named copy
from the KClass
, then fill the arguments for the call, finding the x
parameter by name and putting the x1
value for it, and putting the values obtained from component1()
, component2()
etc. calls or leaving the default values for the other parameters. It would look like this:
fun A.copyX(x1: Int): A {
val copyFunction = this::class.memberFunctions.single { it.name == "copy" }
val args = mapOf(
copyFunction.instanceParameter!! to this,
copyFunction.parameters.single { it.name == "x" } to x1
)
return copyFunction.callBy(args) as A
}
This works because callBy
allows omitting the optional arguments.
Note that it requires a dependency on kotlin-reflect
and works only with Kotlin/JVM. Also, reflection has some performance overhead, so it's not suitable for performance-critical code. You could optimize this by using the Java reflection (this::class.java
, getMethod(...)
) instead (which would be more verbose) and caching the reflection entities.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With