Introduction
Sealed classes are a powerful feature in Kotlin that help developers define restricted class hierarchies. Imagine you have a situation where you only want to allow a specific set of types, and you want to ensure that no other types can be added outside of this set. Sealed classes provide a clean and safe way to achieve this.
Why Sealed Classes Matter
When working with complex data models, especially in scenarios like state management or representing different types of responses, it’s crucial to maintain type safety. Sealed classes offer a way to define a closed set of subclasses, making your code safer and easier to manage. You’ll often see them used in conjunction with when expressions, allowing you to handle each case explicitly, while also ensuring that all potential cases are accounted for.
Concept Explanation
A sealed class serves as a base class that restricts its subclasses. All subclasses must be defined within the same file as the sealed class itself. This limitation ensures that you know all possible subclasses at compile time, which is particularly useful for exhaustive checks in when expressions.
Analogy
Think of a sealed class like a well-organized toolbox. If you have a toolbox for a specific task, you can only add tools that are relevant to that task. Similarly, a sealed class allows you to define a limited set of subclasses that are relevant to a specific context.
Comparison with Other Concepts
- Abstract Classes: Like sealed classes, abstract classes cannot be instantiated, but they allow subclasses to be defined anywhere.
- Enums: Enums define a fixed set of constants, but they cannot hold complex data or behaviors like sealed classes can.
Syntax of Sealed Classes
To declare a sealed class, use the sealed keyword followed by the class name. Here's the basic syntax:
sealed class Shape
Declaring Subclasses
Subclasses of a sealed class are defined within the same file:
sealed class Shape {
class Circle(val radius: Float) : Shape()
class Square(val length: Int) : Shape()
class Rectangle(val length: Int, val breadth: Int) : Shape()
object NotAShape : Shape()
}
In this example, Shape is a sealed class with three subclasses: Circle, Square, and Rectangle. The NotAShape object represents a special case.
Multiple Working Examples
Example 1: Basic Usage of Sealed Classes
Here’s a simple example demonstrating how to use sealed classes to represent different shapes:
sealed class Shape {
class Circle(val radius: Float) : Shape()
class Square(val length: Int) : Shape()
}
fun main() {
val myShape: Shape = Shape.Circle(5.0f)
when (myShape) {
is Shape.Circle -> println("This is a Circle with radius: ${myShape.radius}")
is Shape.Square -> println("This is a Square with length: ${myShape.length}")
}
}
Output:
This is a Circle with radius: 5.0
Example 2: Using Sealed Classes with When Expressions
This example expands on the previous one by calculating areas based on the shape type:
sealed class Shape {
class Circle(val radius: Float) : Shape()
class Square(val length: Int) : Shape()
class Rectangle(val length: Int, val breadth: Int) : Shape()
}
fun calculateArea(shape: Shape): Float {
return when (shape) {
is Shape.Circle -> 3.14f * shape.radius * shape.radius
is Shape.Square -> (shape.length * shape.length).toFloat()
is Shape.Rectangle -> shape.length * shape.breadth.toFloat()
}
}
fun main() {
val circle = Shape.Circle(5.0f)
val square = Shape.Square(4)
val rectangle = Shape.Rectangle(3, 6)
println("Circle Area: ${calculateArea(circle)}")
println("Square Area: ${calculateArea(square)}")
println("Rectangle Area: ${calculateArea(rectangle)}")
}
Output:
Circle Area: 78.5
Square Area: 16.0
Rectangle Area: 18.0
Example 3: Sealed Class with Additional Properties
You can also add more complexity to your sealed classes by including additional properties and methods:
sealed class Shape {
class Circle(val radius: Float) : Shape() {
fun circumference() = 2 * 3.14f * radius
}
class Square(val length: Int) : Shape()
}
fun main() {
val circle = Shape.Circle(5.0f)
println("Circle Circumference: ${circle.circumference()}")
}
Output:
Circle Circumference: 31.400000000000002
Example 4: Adding a "Not a Shape" Case
In some situations, you might want to include a case that represents an invalid state or an absence of a shape:
sealed class Shape {
class Circle(val radius: Float) : Shape()
class Square(val length: Int) : Shape()
object NotAShape : Shape()
}
fun describeShape(shape: Shape): String {
return when (shape) {
is Shape.Circle -> "This is a Circle with radius: ${shape.radius}"
is Shape.Square -> "This is a Square with length: ${shape.length}"
Shape.NotAShape -> "This is not a valid shape."
}
}
fun main() {
val invalidShape = Shape.NotAShape
println(describeShape(invalidShape))
}
Output:
This is not a valid shape.
Common Mistakes
- Defining Subclasses Outside the Sealed Class File:
- Mistake: Attempting to define subclasses in another file.
- Why It's Wrong: Sealed classes require all subclasses to be defined in the same file to maintain type safety.
- Correct Approach: Ensure all subclasses are in the same file as the sealed class.
- Instantiating a Sealed Class Directly:
- Mistake: Trying to create an instance of a sealed class directly.
- Why It's Wrong: Sealed classes are abstract and cannot be instantiated directly.
- Correct Approach: Instantiate the subclasses instead.
- Use Sealed Classes for State Management: They are excellent for representing states in applications, such as loading, success, or error states.
- Avoid Complex Hierarchies: Keep the subclass hierarchy simple to prevent confusion.
- Leverage When Expressions: Always use
whenexpressions with sealed classes to ensure all cases are handled.
Best Practices
Practice Exercises
- Create a sealed class for different types of Animals with subclasses like
Dog,Cat, andBird. Implement a method to print the sound each animal makes. - Implement a simple calculator using a sealed class for different operations like
Addition,Subtraction,Multiplication, andDivision. Handle operations using awhenexpression. - Model a traffic light system using a sealed class with states like
Red,Yellow, andGreen. Create a method that prints actions based on the light's state.
With these exercises, you can deepen your understanding of sealed classes and their practical applications in Kotlin. Happy coding!