Kotlin – Sealed Class ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป

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 ๐Ÿšซโ›”๏ธ

  1. They cannot have public constructors as constructors are set private by default 
  1. 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.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close