Introduction
Sealed classes in Kotlin are a special type of class that allow you to represent restricted class hierarchies. When a value can have one of a limited set of types, you can use a sealed class to define it. Unlike regular classes, all subclasses of a sealed class are known at compile time, which makes it possible to perform exhaustive checks when dealing with them.
Defining a Sealed Class
To define a sealed class, use the sealed
keyword before the class
keyword. Sealed classes can have subclasses, but all subclasses must be defined in the same file as the sealed class.
Syntax
sealed class SealedClass {
// Properties and methods
}
class SubClass1 : SealedClass() {
// Properties and methods
}
class SubClass2 : SealedClass() {
// Properties and methods
}
Example
fun main() {
val shape1: Shape = Circle(5.0)
val shape2: Shape = Rectangle(4.0, 6.0)
describeShape(shape1)
describeShape(shape2)
}
sealed class Shape {
data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
}
fun describeShape(shape: Shape) {
when (shape) {
is Shape.Circle -> println("Circle with radius ${shape.radius}")
is Shape.Rectangle -> println("Rectangle with width ${shape.width} and height ${shape.height}")
}
}
Explanation:
sealed class Shape { ... }
: Defines a sealed classShape
with two subclassesCircle
andRectangle
.describeShape(shape: Shape) { ... }
: Uses awhen
expression to perform exhaustive checks on the type ofshape
.
Output:
Circle with radius 5.0
Rectangle with width 4.0 and height 6.0
Benefits of Sealed Classes
Exhaustive when
Expressions
Since all possible subclasses of a sealed class are known at compile time, when
expressions can be exhaustive, ensuring that all cases are handled.
Type Safety
Sealed classes provide better type safety by restricting the hierarchy to a known set of subclasses.
Example Program with Sealed Classes
Here is an example program that demonstrates various aspects of sealed classes in Kotlin:
Defining a Sealed Class for Network Responses
fun main() {
val success = NetworkResponse.Success("Data loaded successfully")
val error = NetworkResponse.Error(404, "Not Found")
val loading = NetworkResponse.Loading
handleResponse(success)
handleResponse(error)
handleResponse(loading)
}
sealed class NetworkResponse {
data class Success(val data: String) : NetworkResponse()
data class Error(val errorCode: Int, val message: String) : NetworkResponse()
object Loading : NetworkResponse()
}
fun handleResponse(response: NetworkResponse) {
when (response) {
is NetworkResponse.Success -> println("Success: ${response.data}")
is NetworkResponse.Error -> println("Error ${response.errorCode}: ${response.message}")
is NetworkResponse.Loading -> println("Loading...")
}
}
Explanation:
sealed class NetworkResponse { ... }
: Defines a sealed classNetworkResponse
with three subclassesSuccess
,Error
, andLoading
.handleResponse(response: NetworkResponse) { ... }
: Uses awhen
expression to handle different types of network responses.
Output:
Success: Data loaded successfully
Error 404: Not Found
Loading...
Using Sealed Classes for Algebraic Data Types
Sealed classes can also be used to represent algebraic data types, which are commonly used in functional programming.
fun main() {
val number: Expression = Add(Number(3), Number(5))
val result = evaluate(number)
println("Result: $result")
}
sealed class Expression
data class Number(val value: Int) : Expression()
data class Add(val left: Expression, val right: Expression) : Expression()
data class Subtract(val left: Expression, val right: Expression) : Expression()
fun evaluate(expr: Expression): Int {
return when (expr) {
is Number -> expr.value
is Add -> evaluate(expr.left) + evaluate(expr.right)
is Subtract -> evaluate(expr.left) - evaluate(expr.right)
}
}
Explanation:
sealed class Expression { ... }
: Defines a sealed classExpression
with three subclassesNumber
,Add
, andSubtract
.evaluate(expr: Expression): Int { ... }
: Uses awhen
expression to evaluate different types of expressions.
Output:
Result: 8
Extending Sealed Classes
Sealed classes can be extended within the same file where they are defined. This ensures that all possible subclasses are known at compile time.
Example
fun main() {
val vehicle1: Vehicle = Car("Toyota")
val vehicle2: Vehicle = Bike("Honda")
describeVehicle(vehicle1)
describeVehicle(vehicle2)
}
sealed class Vehicle {
data class Car(val brand: String) : Vehicle()
data class Bike(val brand: String) : Vehicle()
}
fun describeVehicle(vehicle: Vehicle) {
when (vehicle) {
is Vehicle.Car -> println("Car brand: ${vehicle.brand}")
is Vehicle.Bike -> println("Bike brand: ${vehicle.brand}")
}
}
Explanation:
sealed class Vehicle { ... }
: Defines a sealed classVehicle
with two subclassesCar
andBike
.describeVehicle(vehicle: Vehicle) { ... }
: Uses awhen
expression to handle different types of vehicles.
Output:
Car brand: Toyota
Bike brand: Honda
Conclusion
In this chapter, you learned about sealed classes in Kotlin, including how to define sealed classes, their benefits, and how to use them in various scenarios. Sealed classes provide a powerful way to represent restricted class hierarchies, enabling exhaustive when
expressions and better type safety. Understanding and applying sealed classes is crucial for writing robust and maintainable Kotlin programs.