I'm a beginner in Kotlin and recently read about Sealed Classes. But from the doc the only think I actually get is that they are exist.
The doc stated, that they are "representing restricted class hierarchies". Besides that I found a statement that they are enums with superpower. Both aspects are actually not clear.
So can you help me with the following questions:
UPDATE: I carefully checked this blog post and still can't wrap my head around that concept. As stated in the post
Benefit
The feature allows us to define class hierarchies that are restricted in their types, i.e. subclasses. Since all subclasses need to be defined inside the file of the sealed class, there’s no chance of unknown subclasses which the compiler doesn’t know about.
Why the compiler doesn't know about other subclasses defined in other files? Even IDE knows that. Just press Ctrl+Alt+B
in IDEA on, for instance, List<>
definition and all implementations will be shown even in other source files. If a subclass can be defined in some third-party framework, which not used in the application, why should we care about that?
Say you have a domain (your pets) where you know there is a definite enumeration (count) of types. For example, you have two and only two pets (which you will model with a class called MyPet
). Meowsi is your cat and Fido is your dog.
Compare the following two implementations of that contrived example:
sealed class MyPet class Meowsi : MyPet() class Fido : MyPet()
Because you have used sealed classes, when you need to perform an action depending on the type of pet, then the possibilities of MyPet
are exhausted in two and you can ascertain that the MyPet
instance will be exactly one of the two options:
fun feed(myPet: MyPet): String { return when(myPet) { is Meowsi -> "Giving cat food to Meowsi!" is Fido -> "Giving dog biscuit to Fido!" } }
If you don't use sealed classes, the possibilities are not exhausted in two and you need to include an else
statement:
open class MyPet class Meowsi : MyPet() class Fido : MyPet() fun feed(myPet: MyPet): String { return when(myPet) { is Mewosi -> "Giving cat food to Meowsi!" is Fido -> "Giving dog biscuit to Fido!" else -> "Giving food to someone else!" //else statement required or compiler error here } }
In other words, without sealed classes there is not exhaustion (complete coverage) of possibility.
Note that you could achieve exhaustion of possiblity with Java enum
however these are not fully-fledged classes. For example, enum
cannot be subclasses of another class, only implement an interface (thanks EpicPandaForce).
What is the use case for complete exhaustion of possibilities? To give an analogy, imagine you are on a tight budget and your feed is very precious and you want to ensure you don't end up feeding extra pets that are not part of your household.
Without the sealed
class, someone else in your home/application could define a new MyPet
:
class TweetiePie : MyPet() //a bird
And this unwanted pet would be fed by your feed
method as it is included in the else
statement:
else -> "Giving food to someone else!" //feeds any other subclass of MyPet including TweetiePie!
Likewise, in your program exhaustion of possibility is desirable because it reduces the number of states your application can be in and reduces the possibility of bugs occurring where you have a possible state where behaviour is poorly defined.
Hence the need for sealed
classes.
Note that you only get the mandatory else
statement if when
is used as an expression. As per the docs:
If [
when
] is used as an expression, the value of the satisfied branch becomes the value of the overall expression [... and] theelse
branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions
This means you won't get the benefit of sealed classes for something like this):
fun feed(myPet: MyPet): Unit { when(myPet) { is Meowsi -> println("Giving cat food to Meowsi!") // not an expression so we can forget about Fido } }
To get exhaustion for this scenario, you would need to turn the statement into an expression with return type.
Some have suggested an extension function like this would help:
val <T> T.exhaustive: T get() = this
Then you can do:
fun feed(myPet: MyPet): Unit { when(myPet) { is Meowsi -> println("Giving cat food to Meowsi!") }.exhaustive // compiler error because we forgot about Fido }
Others have suggested that an extension function pollutes the namespace and other workarounds (like compiler plugins) are required.
See here for more about this problem.
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