Kotlin’s Sealed Class: Enums on Steroids

Sealed Classes vs Enums

Sealed classes are another new thing we got in Kotlin that Java didn’t give us, but what is it and how can we use it to improve our code?

According to kotlinlang.org, here’s the definition of Sealed classes

“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.”

Hold on, that sounds a whole lot like enums! Sealed classes are more or less, an extension of enums: class with a restricted set of values.

What’s the difference then? Each enum can only contain a single instance, whereas sealed classes can contain multiple instances which can contain state. What does this entail?

sealed class ActivityState {
    object Idle: ActivityState()
    data class Loading(val content: List<String>): ActivityState()
    data class Error(val error: Throwable): ActivityState()

Here’s my simple example of a sealed class. Compared to an enum, first thing you’d see is that each item isn’t a constant, but rather an object or class. The objects in this sealed class are what I’d call stateless items. They will be the same every time you instantiate them. The data classes however is where we mean we can maintain state, they can hold values. If they were all objects, we’d practically be building an enum.

Another sweet advantage of making it a data class is that in unit tests and such, we can advantage of the equals function built into data classes if we want to compare our sealed classes.

Sealed Classes in When Expressions

As with enums, sealed classes are best used in a when expression.

fun loadData(activityState: ActivityState) = when (activityState) {
    ActivityState.Idle -> { /* Idle: do nothing */ }
    is ActivityState.Loading -> { loadContent(activityState.content) }
    is ActivityState.Error -> { handleError(activityState.error) }

As with enums, when used as an expression, the Kotlin compiler will force you to be exhaustive so you have to cover all options by either handling cases for all of your sealed class items, or by using an else branch. From this, you might already be noticing some key differences:

  1. For the Loading and Error branches, we’re using ‘is’. This is because we’re comparing classes, as opposed to Idle which is just an object.
  2. In the Loading and Error branches, we’re able to pass in the data we set in their constructors in the sealed class.

That’s all the key information you need to know about Sealed classes, but there a few important pointers as well, again from Kotlinlang:

  • A sealed class can have subclasses, but all of them must be declared in the same file as the sealed class itself.
  • A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.
  • Sealed classes are not allowed to have non-private constructors (their constructors are private by default).
  • Classes which extend subclasses of a sealed class (indirect inheritors) can be placed anywhere, not necessarily in the same file.

And that pretty much sums up sealed classes. Happy coding (ง ͠° ͟ل͜ ͡°)ง