What are sealed classes?
A Sealed
class is a class that restricts the inheritance hierarchy by allowing a fixed set of subclasses. All the subclasses must be declared within the same file as the sealed class itself. These subclasses can be of any type a data class, an object, a regular class, or another sealed class. Sealed classes are useful when you want to represent a limited set of related classes or types and ensure that all possible subclasses are known at compile time. Third-party clients can’t extend sealed class.
The sealed class
in Kotlin has several key features that make :
- Type Safety at Compile-Time: Sealed classes ensure type safety by restricting the types to be matched at compile-time, preventing the introduction of new subclasses at runtime.
- Subclass Definition Scope: Subclasses of a sealed class must be defined within the same Kotlin file or any scope where the sealed class is visible. They are not required to be defined directly within the sealed class.
- Restriction on Inheritance: Sealed classes cannot be inherited or extended by other classes. They restrict the inheritance hierarchy to a predefined set of subclasses.
- Limited Set of Subclasses: Sealed classes allow for a limited set of subclasses, enhancing code predictability and maintainability.
- Representation of States: Sealed classes are often used to represent a set of possible states or outcomes in a program.
- Constructor, Properties, and Methods: Sealed classes can have constructors, properties, and methods similar to regular classes.
- Protected Constructor: The constructor of a sealed class is implicitly protected by default. It can also be explicitly marked as private, but public and internal visibility is not allowed.
- Instantiation Restriction: Sealed classes cannot be instantiated directly; they must be instantiated through one of their subclasses.
How to declare and use sealed classes?
To declare a sealed class in Kotlin, you use the sealed
keyword followed by the name of the class. The sealed class can have one or more subclasses, and all of these subclasses are defined within the same file where the sealed class is declared. Here’s the basic syntax:
sealed class MySealedClass {
// Subclasses go here
}
You then define the subclasses inside the sealed class using the data class
or object
keyword, depending on whether you need instances with data or singleton objects. Here’s an example:
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
In this example, Result
is a sealed class with three subclasses: Success
, Error
, and Loading
. Success
and Error
are data classes, each carrying specific data, while Loading
is an object representing a loading state.
Example of a Sealed Class
Let’s consider an example where a sealed class is used to model different states of an order in an e-commerce system:
sealed class OrderState {
object Pending : OrderState()
data class Shipped(val trackingNumber: String) : OrderState()
data class Delivered(val deliveryDate: String) : OrderState()
object Canceled : OrderState()
}
The OrderState
sealed class has four subclasses: Pending
, Shipped
, Delivered
, and Canceled
, representing different states an order can be in. Shipped
and Delivered
have additional data associated with them (trackingNumber
and deliveryDate
).
fun processOrder(orderState: OrderState) {
when (orderState) {
is OrderState.Pending -> println("Order is pending processing.")
is OrderState.Shipped -> println("Order has been shipped. Tracking number: ${orderState.trackingNumber}")
is OrderState.Delivered -> println("Order has been delivered on ${orderState.deliveryDate}.")
is OrderState.Canceled -> println("Order has been canceled.")
}
}
fun main() {
val pendingOrder = OrderState.Pending
val shippedOrder = OrderState.Shipped(trackingNumber = "123456789")
val deliveredOrder = OrderState.Delivered(deliveryDate = "2022-03-01")
val canceledOrder = OrderState.Canceled
processOrder(pendingOrder)
processOrder(shippedOrder)
processOrder(deliveredOrder)
processOrder(canceledOrder)
}
Explanation:
The processOrder
function handles the processing of orders based on their state. It takes a OrderState
parameter. The when
expression in the processOrder
function is used to perform different actions based on the type of the orderState
parameter((Pending, Shipped, or Delivered, Canceled). It checks the type of orderState
using is
checks for each possible subclass of the OrderState
sealed class and executes the corresponding block of code based on the type.
Main Function (main
): In the main
function, instances of different order states are created, and their processing is demonstrated using the processOrder
function.
What are the Similarities and difference between sealed and enum classes?
Similarities
- Both sealed classes and enum classes provide a way to define a limited set of possible values or states.
- They are useful when you want to ensure that a variable can only take on a specific set of values.
Differences
Feature | Sealed Class | Enum Class |
---|---|---|
Number of Instances | Multiple instances for each subclass. | Single instance for each enum constant. |
Data and Behavior | Each subclass can have properties and methods. | Enum constants can have properties and methods. |
Instantiation | Subclasses can be instantiated with different data. | Enum constants are typically singletons. |
Nested Classes | Allows the declaration of nested classes within the sealed class. | Does not allow nested classes within the enum class. |
Use Case | Used for representing a restricted set of related states or outcomes. | Used for representing a fixed set of distinct values or constants. |
Flexibility | More flexible in terms of data and behavior within each subclass. | Less flexible, but provides a fixed set of named values. |
Pattern Matching | Often used with when expressions for exhaustive pattern matching. | Also used with when expressions, but the set of values is known at compile time. |
Examples | kotlin sealed class Result { ... } | kotlin enum class Color { RED, GREEN, BLUE } |
Advantages of Sealed Classes in Android Kotlin:
- Code Clarity: Sealed classes make the code more readable and self-explanatory by explicitly defining a restricted set of possibilities.
- Exhaustive Handling: The use of sealed classes with
when
expressions ensures that all possible cases are handled, providing better compile-time safety. - Maintenance: Sealed classes can simplify maintenance by encapsulating related classes within a single hierarchy, making it easier to manage changes and additions to the code.
- Pattern Matching: Sealed classes facilitate pattern matching, making it easier to express logic based on the type of an object in a concise and readable way.