Kotlin has a wide range of wonderful features that make programming a lot easier for us. One of the features I have been playing around with are Sealed classes. The concept of sealed classes was never in java and it is one of the new toys in Kotlin that has many benefits. Sealed classes are โEnumsโ with super powers ๐ฆธ๐พโโ๏ธ. I will explain what they are, the advantages of sealed classes and a simple example to help you get started using them.
Letโs begin โฆ
What are Sealed Classes ?
In the Kotlin doc:
โSealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other typeโ
Sealed classes are enums with a few extra benefits or super powers. One advantage is that the subclasses of sealed class can have different states as enums cannot allow us to pass additional information with each call. You can do a hack but it’s a hack ( ๐คข), we do not want to add hacks to our code ๐คฅ. Also sealed class can be inherited from, while enums cannot as they are final. With enums we have only one object per type but with sealed classes we can have several objects of the same class, this allows us to be very flexible when declaring our classes with our sealed class.
All the advantages help us to have a base representation of the class and then customise and adjust them in our subclasses. Like I said, it offers us flexibility.
Few rules to remember ๐ซโ๏ธ
- They cannot have public constructors as constructors are set private by default
- When declaring a sealed class, the subclasses have to be inside the superclass or in the same file.
Example
//Declare sealed class - Avenger.kt
sealed class Avenger
Very simple just write “sealed” before your class declaration.
//Create your subclasses Avenger.kt
sealed class Avenger { //sealed modifier to declare sealed class
class TonyStark()
}
class TonyStark : Avenger()
You can declare your subclasses inside the parent class or outside. But for this example we will declare the subclasses outside the parent class just to make it clear and simple.
//Avenger.kt
sealed class Avenger
//Another file JusticeLeague.kt This will not work
class TonyStark : Avenger()
You have to declare the subclasses either inside the sealed class, parent class, or in the same Kotlin file. The sealed class declared is private constructor so you will not be able to access it outside . So the above will not work ๐ฎ๐พโโ๏ธ๐จ๐
//sealed class can have abstract methods Avenger.kt
sealed class Avenger {
abstract fun alias(): String
}
A good advantage of sealed class is that we can declared abstract methods and of course this tells the subclass they must have alias method.
//Avenger.kt
sealed class Avenger {
abstract fun alias(): String
}
class TonyStark : Avenger() {
override fun equals(other: Any?): Boolean = this == Any()
override fun hashCode(): Int = 2008
override fun alias(): String = "Iron Man"
}
Now our TonyStark class has the implemented alias method inherited from our sealed class. Note when using an empty class extending sealed class, the IDE complains about the class not having a state or no overridden equal and hash code method. I have added equals method and hash code method to keep the IDE happy and to make us happy. In truth we can just use data class but I wanted to show you that you can use a normal class that extends our sealed class but with a caveat.
//Avenger.kt
sealed class Avenger {
abstract fun alias(): String
}
class TonyStark : Avenger() {
override fun equals(other: Any?): Boolean = this == Any()
override fun hashCode(): Int = 2008
override fun alias(): String = "Iron Man"
}
data class SteveRodgers(val heroOrVillain: Boolean) : Avenger() {
override fun alias(): String = "Captain America"
fun determineStatus() = if (heroOrVillain) "America's hero" else "International Criminal"
}
data class BruceBanner(val angry: Boolean) : Avenger() {
override fun alias(): String = "Incredible Hulk"
fun smash(): String = if (angry) "Hulk Smash" else "Puny Banner"
}
data class TChalla(val vibraniumLevel: Int) : Avenger() {
override fun alias(): String = "Black Panther has this amount of Vibranium Level, $vibraniumLevel"
}
As you can see our subclasses can have different sets of data passed in their constructor and have their own dedicated functions. This has an advantage over Enums as you have to pass the same sets of data in each subset of enums even if one set does not need them.
For this example lets first create a list of of Avengers.
//Avenger.kt
val avengers: List<Avenger> = listOf(
TonyStark(),
SteveRodgers(true),
BruceBanner(true),
TChalla(1000))
Now lets run it in our main function
fun main() {
val avengers: List<Avenger> = listOf(
TonyStark(),
SteveRodgers(true),
BruceBanner(true),
TChalla(1000)
)
avengers.forEach {
val aliasId: String = when (it) {
is TonyStark -> {
println("Iron Man Year of first film : ${it.hashCode()}")
it.alias() + "\n"
}
is SteveRodgers -> {
println("Hero or Villain: ${it.determineStatus()}")
it.alias() + "\n"
}
is BruceBanner -> {
println("What does Hulk do ? ${it.smash()}")
it.alias() + "\n"
}
is TChalla -> {
it.alias() + "\n"
}
}
println(aliasId)
}
}
//Output
Iron Man Year of first film : 2008
Iron Man
Hero or Villain: America's hero
Captain America
What does Hulk do ? Hulk Smash
Incredible Hulk
Black Panther has this amount of Vibranium Level, 1000
Look at the power of the sealed classes in our when expression, handle each case of our Avenger sealed class type and no else branch too, also a tip โ๐พ๐กthe IDE can complete the when expression for you, but you will need an else branch when using it in a statement.
An Android example could be used when you have different view types in your adapter.
e.g.
//Android world
override fun getItemViewType(position: Int): Int {
return when (avengers[position]) {
is TonyStark -> R.layout.iron_man
is SteveRodgers -> R.layout.captain_america
//Remaining Avenger Type
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.iron_man -> {
val itemView = layoutInflater.inflate(R.layout.iron_man, parent, false)
IronMan(itemView)
}
R.layout.captain_america -> {
val itemView = layoutInflater.inflate(R.layout.captain_america, parent, false)
CaptainAmerica(itemView)
}
.....
else -> throw RuntimeException("Unknown Super hero View")
}
}
Hopefully you gained something from this explanation about sealed classes. ๐จ๐พโ๐ปLet me know your thoughts and tips.