Significance of Sealed Class over Enum and Abstract Classes

Vinod Pattanshetti
5 min readJun 2, 2023

--

Image source: www.google.com

Most of us know Kotlin has become a popular language among mobile developers and it has provided a lot of features, Kotlin Sealed classes in one of them. Let us discuss how Kotlin Sealed class works and the advantages of Sealed classes over Enum classes and Abstract classes. First, we will see what are the Sealed classes.

What are the Sealed Classes?

Sealed classes allow us to write restricted class hierarchies and restrict developers from creating new subclasses. Sealed classes are useful when we have a strict inheritance hierarchy with a possible set of subclasses. A class can be declared as sealed using the keyword sealed before the class name. The constructors of sealed classes are private in nature and we can’t write non-private class constructors. The subclasses of sealed classes must be declared in the same file in which the sealed class itself. The compiler will get to know about all subclasses at compile time not at runtime. The subclasses of a sealed class should appear within a module in which a sealed class is defined not outside a module.

For example, third-party clients can not extend your Sealed class in their code. Each instance of a sealed class has a type from a possible limited set that is known when the sealed class is compiled.

sealed class ApiCallResult<T>(val apiData: T? = null, val errorMessage: String? = null) {
class Success<T>(apiData: T) : ApiCallResult<T>(apiData)
class Error<T>(errorMessage: String?, apiData: T? = null) : ApiCallResult<T>(apiData)
class UILoading<T> : ApiCallResult<T>()
}

So, above I have created a sealed class ApiCallResult(the class is using generics to support all types of different response types) which has restricted possible subclasses you need to call a remote network API call. When you call a network API once it is successful, it will return apiData. When there is some error/exception, it will return the errorMessage and the UILoading subclass will render a progress bar or loading mechanism on the Activity or UI screen until the API call gets completed.

Below is the code snippet to call network API calls from view models.

fun callNetworkApiData() {
viewModel.response.observe(viewLifecycleOwner) { response ->
when (response) {
is ApiCallResult.Success -> {
response.data?.let {
//bind the data to the UI when api is success
}
}
is ApiCallResult.Error -> {
//Show error message in case of network api failure.
}

is ApiCallResult.Loading -> {
//show loader, shimmer effect until network api call
}
}
}
}

Sealed class over Enum class

Using sealed classes you can write different types of subclasses and also contain the state.

1. An Enum type is a special data type that enables a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it.

2. Enum types are constants and you can have only one argument so it is difficult to maintain different states of the instances.

enum class ApiCallResult(val message: String) {
Success("Success"),
Error(val exception: Exception) // not possible with enums
}

3. In the above example, Enum class ApiCallResult we are storing only the string type values attached to the Enum class, but what if in the error case, we want to display the actual exception instead of the constant string type that has caused the error. It’s not possible with Enums since Enum types can’t hold the state of the type like sealed classes.

4. The subclasses of a sealed class can be normal classes, data classes, or sealed classes themselves so it is very easy to contain the state of the subclasses.

Let us consider another example

sealed class Shape() {
class Circle(var radius:Float):Shape()
class Square : Shape()
data class Rectangle(var l: Double, var b: Double): Shape()
object NotAShape:Shape()
}

This is how we can have the state(length, breadth, etc) in the sealed class, which is impossible with an enum.

Sealed class over Abstract class

Sealed classes are abstract by default and also sealed classes can’t be instantiated directly so now we can think that why we can’t use Abstract classes instead of sealed classes. What are the advantages Sealed classes provide over abstract classes?

  1. Well, the first advantage of using a Sealed class over an Abstract class here is Sealed class should contain all the hierarchies(subclasses) in the same file or module whereas an Abstract class can have its hierarchies(subclasses) anywhere in the project.
  2. If we add a rectangle subclass extending the sealed class Shape in a different file then we will be getting compile- time error. The compile-time error means a sealed class forcing us to add a restricted number of hierarchies by providing required classes within the same file where the sealed class is added.
  3. Another significant advantage of using a sealed class over an abstract class is that it helps the IDE to understand the different types(subclassed i.e. hierarchies) involved and thereby helps the developer in auto-filling and avoiding spelling mistakes.
val shape: Shape = Rectangle(10, 12)
val value = when(shape) {
is Circle -> {
"You have selected circle shape!"
}
is Square -> {
"You have selected Square shape!"
}
is Rectangle -> {
"You have selected Rectangle shape!"
}
is NotAShape -> {
"You have selected NotAShape shape!"
}
//no else case is required since all cases are handled
}

4. We can take advantage of the auto-fill feature in these cases when we use the “when” block in the case of abstract classes we need to provide an else block explicitly but when we sealed the class there is no requirement to add an else case because IDE is already aware of restricted hierarchies defined.

When we go with abstract classes, IDE allows adding hierarchies from various files or modules in the project, and hence, the IDE can not understand the different subclasses involved. Hence, the compiler throws an error stating that another case should be mentioned explicitly. This is another benefit of using a Sealed class over an abstract class.

You can see in the above image you will see the error ‘when’ expression must be exhaustive, add necessary ‘else’ branch when we use an abstract class instead of a sealed class we get this error. In the above image, IDE has no clue if all the hierarchies are covered or not covered. This is the predefined limited hierarchy advantage using a Sealed class.

Summary

Sealed classes allow us to write restricted class hierarchies so all direct subclasses(hierarchies) are known at compile time. Subclasses(hierarchies) can be normal classes, data classes, objects (singleton in Kotlin), or sealed classes themselves. The sealed class has State as compared to the enum class. The enum type doesn’t have State, enum can be used to define predefined constants. Sealed classes allow hierarchies to be part of the same file but that is not possible in the case of abstract classes. Sealed classes support the IDE auto-fill feature.

--

--

Vinod Pattanshetti
Vinod Pattanshetti

Written by Vinod Pattanshetti

Senior Software Engineer(Android Developer)

No responses yet