Enum vs Sealed Classes

Enum vs Sealed Classes

The article aims to explain the below list of things:

  1. Enum Classes
  2. Why do we use enum classes in the first place ?
  3. How to use enum classes ?
  4. Usage of enum classes using when block
  5. Problems with the enum classes
  6. Sealed Classes
  7. How to use sealed classes ?
  8. Usage of sealed classes using when block
  9. When to use what !

Enum Classes

When we want to represent a fixed set of constants, we tend to use enums. For instance, days of the week, network status, set of colors, etc.

Why do we use enum classes in the first place ?

  1. Here is a great answer to explain why we use enums on StackOverFlow
  2. Here is the outline of that answer:
  3. When you want to restrict a variable to a set of values, you can use enums. For instance, if we have a variable named "day" and we want the user to choose a value from the set of days out of a week enum , then we restrict the variable to choosing from the given set of values.
  4. Another article from which you can gain a better understanding of this point is StackOverFlow

How to use enum classes ?

We define enum classes using the enum keyword followed by the name of the class.

enum class Size {
    SMALL,
    MEDIUM,
    LARGE,
}

We can also add constructor parameters to our enum class.

enum class Size(weight: Int) {
    SMALL(weight = 8),
    MEDIUM(weight = 16),
    LARGE(weight = 32),
}

Usage of enum using when block

  • We can't create objects of the enum class because internally it is implemented as an abstract class.
  • We can also hold state (data) here as we have passed cost.
enum class Size(val cost: Int) {
    SMALL(cost = 8),
    MEDIUM(cost = 16),
    LARGE(cost = 32),
}

fun handleSize(coffeeSize: Size) {
    when (coffeeSize) {
        Size.SMALL -> {
            println("Cost of small coffee: ${coffeeSize.cost} $")
        }
        Size.MEDIUM -> {
            println("Cost of medium coffee: ${coffeeSize.cost} $")
        }
        Size.LARGE -> {
            println("Cost of large coffee: ${coffeeSize.cost} $")
        }
    }
}

fun main() {
    handleSize(Size.MEDIUM)
    // Output
    // Cost of medium coffee: 16 $
}
  • Enums can be used in when in Kotlin. when can be used either as a statement or an expression.
when (coffeeSize) {
    Size.SMALL -> {
        println("Cost of small coffee: ${coffeeSize.cost} $")
    }
    Size.MEDIUM -> {
        println("Cost of medium coffee: ${coffeeSize.cost} $")
    }
}
  • In the above code when is used as statement. So it will ignore the fact that we have missed Size.LARGE or an else block
val size = when (coffeeSize) {
    Size.SMALL -> {
        println("Cost of small coffee: ${coffeeSize.cost} $")
    }
    Size.MEDIUM -> {
        println("Cost of medium coffee: ${coffeeSize.cost} $")
    }
}
  • If we consider all the constants of enum inside when block as expression, we don't have to add the else block even if we add it, it will be considered redundant.

Problem with the enum classes

  • It can only hold single type of state(data) through out.
enum class Network(val response : Boolean){
    SUCCESS(true)
    FAILURE(false)
}
  • There are two constants in the code sample above: SUCCESS and FAILURE. If a network call succeeds, we pass true; if it fails, we pass false.
  • But what if we wanted to transmit an exception or message object when a network call failed?

enum class Network(val response : Boolean){
    SUCCESS(true)
    FAILURE("No Internet Connection") // error
}
  • Because we are giving a string instead of a boolean as the response, the code sample above will fail.
  • Enum classes should be used when we are certain that comparable types of state and constants will be encountered because enum is not flexible in such a situation and can only maintain similar types of state to itself.

Sealed classes

  1. A sealed class allows you to represent constrained hierarchies in which an object can only be of one of the given types.
  2. A sealed class defines a set of subclasses within it.
  3. A sealed class ensures type safety by restricting the type to being matched at compile time instead of at runtime.

Why to use sealed classes ?

  • In some ways, sealed classes are identical to enum classes, but as we mentioned before, enum classes only exist as a single instance/state, whereas subclasses of enum classes might have several instances, each with its own state.
  • Each subclass can have their own set of constructor parameter which is not possible in enum state.

How to use sealed classes ?

  • Subclasses of sealed classes can have different set of constructor parameter
  • We define sealed classes using sealed keyword followed by the name of class
sealed class NetworkState{
    data class Success(val data : String) : NetworkState()
    data class Error(val error : Throwable) : NetworkState()
    object Loading : NetworkState()
}

Usage of sealed classes using when block

  • Subclasses of sealed classes can have different set of constructor parameter
  • Just like when, in the case of enums, when as a statement is not exhaustive but when as an expression is exhaustive in the case of sealed classes as well. To understand what is exhaustive ?
  • Sealed classes can also have all object declaration subclasses, and when used this way, it is very similar to an enum.
sealed class NetworkState{
    data class Success(val data : String) : NetworkState()
    data class Error(val error : Throwable) : NetworkState()
    object Loading : NetworkState()
}

fun getNetworkState(networkState : NetworkState) :  Any{
    return when(networkState){

        is NetworkState.Success -> {
            networkState.data
        }

        is NetworkState.Error -> {
            networkState.error
        }

        networkState.Loading -> {
            "Loading..."
        }
    }
}

fun main(){
    val ans = getNetworkState(NetworkState.Success(data = "Network Response"));
    println(ans)
}

When to use what ?

  • When we expect to see similar types of states (data) and constants, we should implement enum classes.
  • When dealing with different states (data), as was the case in our NetworkResponse, we should use sealed classes since they allow us to have separate types for success and error instances.

Did you find this article valuable?

Support Krish Parekh by becoming a sponsor. Any amount is appreciated!