[Kotlin Pearls 2] Sealed Class Override
An overlooked useful feature
Disclaimer: I have zero time for writing the blog because of other projects, of which I will disclose more next month.
So I am trying to write a few micro-posts about Kotlin idioms and little-known features, hoping they may be useful to someone. Nothing really original, just tricks I have stolen from smarter guys.

Kotlin has a great feature called sealed class, which allow us to extend an abstract class in a set of fixed concrete types defined in the same compilation unit (a file). In other words, is not possible to inherit from the abstract class without touching the file where it is defined.
You may wonder: why having our freedom restricted is a great feature?
The answer is that in this way the compiler can check we have considered all the possible cases when taking into account the subtypes. Another advantage is that prevent users of a library to extend the class in any unplanned way.
For example, let’s say we want to have a class representing the power tools types for a DIY store.
We can write this code:
sealed class PowerTool()data class CircularSaw(val diameter: Int, val cordless: Boolean, val name: String, val price: Double): PowerTool()data class DrillPress(val rpm: Int, val name: String, val price: Double): PowerTool()//...etc
But in this way, we have to repeat name and price on every subtype.
Not only this is inconvenient, but it also prevents us to read them if we have a generic PowerTool reference.
For example to read the price of a PowerTool we need to write this quite boring code:
fun getPrice(tool: PowerTool): Double =
when(tool){
is CircularSaw -> tool.price
is DrillPress -> tool.price
}
What we want to write is something like this:
sealed class PowerTool(val name: String, val price: Double)data class CircularSaw(val diameter: Int, val cordless: Boolean, override val name: String, override val price: Double): PowerTool()data class DrillPress(val rpm: Int, override val name: String, override val price: Double): PowerTool()Error:(10, 38) Kotlin: 'name' in 'PowerTool' is final and cannot be overridden
Error:(10, 65) Kotlin: 'price' in 'PowerTool' is final and cannot be overridden
Unfortunately, it doesn’t compile.
One possible workaround is to duplicate the field in this way.
sealed class PowerTool(val name: String, val price: Double)data class CircularSaw(val diameter: Int, val cordless: Boolean, val name1: String, val price1: Double): PowerTool(name1, price1)data class DrillPress(val rpm: Int, val name1: String, val price1: Double): PowerTool(name1, price1)
It solves the problem of PowerTool instance, but it is quite ugly and unnecessarily verbose.
There is a good solution though. Kotlin allows us to put properties on interfaces and we can override them without problems in the data classes.
interface Article {
val name: String
val price: Double
}sealed class PowerTool: Articledata class CircularSaw(val diameter: Int, val cordless: Boolean, override val name: String, override val price: Double): PowerTool()data class DrillPress(val rpm: Int, override val name: String, override val price: Double): PowerTool()
If you have found it useful please clap here and I will publish other posts like this. Please also comment if you prefer other solutions.
Update (11/2/2019)
In the comments, they suggested additional solutions.
Making the fields as abstract in the sealed class:
sealed class PowerTool {
abstract val name: String
abstract val price: Double
}data class CircularSaw(val diameter: Int, val cordless: Boolean, override val name: String, override val price: Double): PowerTool()data class DrillPress(val rpm: Int, override val name: String, override val price: Double): PowerTool()
Or mark them open:
sealed class PowerTool(open val price: Double, open val name: String)
data class CircularSaw(val diameter: Int, val cordless: Boolean, override val name: String, override val price: Double): PowerTool(price, name)
data class DrillPress(val rpm: Int, override val name: String, override val price: Double): PowerTool(price, name)
In this specific case, I prefer the solution with the interface but it depends on your personal taste and specific context.