9. What are sealed classes in Kotlin and how are they used?

Basic

9. What are sealed classes in Kotlin and how are they used?

Overview

Sealed classes in Kotlin are used to represent restricted class hierarchies where a class can have a limited set of subclasses. They are primarily used for representing fixed types in a more type-safe manner, especially useful in when expressions and pattern matching. Understanding sealed classes is essential for Kotlin developers to create more robust and maintainable applications.

Key Concepts

  1. Restricted Class Hierarchies: Sealed classes allow defining a closed set of subclasses.
  2. Type Safety: They enhance type safety by enabling the compiler to use more information about the types, reducing bugs.
  3. Use in when Expressions: Sealed classes are beneficial in when expressions, allowing the compiler to check for missing branches.

Common Interview Questions

Basic Level

  1. What is a sealed class in Kotlin?
  2. How do you declare a sealed class and its subclasses?

Intermediate Level

  1. How do sealed classes compare to enums in Kotlin?

Advanced Level

  1. How can sealed classes be used in pattern matching with when expressions?

Detailed Answers

1. What is a sealed class in Kotlin?

Answer: A sealed class in Kotlin is a type of class that restricts which classes can inherit from it. It is used to define a closed set of subclasses within the same file. Sealed classes are abstract by nature; they cannot be instantiated directly and can have abstract members. They are useful for representing a fixed number of types and enhance compile-time checking, ensuring exhaustive when expression handling.

Key Points:
- Sealed classes cannot be instantiated.
- All subclasses must be declared in the same file.
- Promotes exhaustive use in when expressions, enhancing type safety.

Example:

sealed class Result
data class Success(val data: String): Result()
data class Failure(val error: Exception): Result()

fun handleResult(result: Result) {
    when (result) {
        is Success -> println(result.data)
        is Failure -> println(result.error.message)
    }
}

2. How do you declare a sealed class and its subclasses?

Answer: To declare a sealed class in Kotlin, you use the sealed modifier before the class keyword. Subclasses of a sealed class can either be classes or data classes, and they must be declared in the same Kotlin file as the sealed class. This limitation ensures that all subclasses are known to the compiler at compile time, which is crucial for when expressions.

Key Points:
- Use the sealed modifier.
- Subclasses must be in the same file.
- Subclasses can be objects, classes, or data classes.

Example:

sealed class Payment

data class CreditCard(val number: String, val expiryDate: String): Payment()
data class PayPal(val email: String): Payment()
object Cash : Payment()

fun processPayment(payment: Payment) {
    when (payment) {
        is CreditCard -> println("Processing credit card")
        is PayPal -> println("Processing PayPal")
        is Cash -> println("Processing cash")
    }
}

3. How do sealed classes compare to enums in Kotlin?

Answer: Sealed classes and enums in Kotlin are both used to define a restricted set of types. However, sealed classes offer more flexibility by allowing each subclass to hold its own state and behavior. Enums are more memory-efficient for simple lists of constants without additional data or behavior. Sealed classes are preferable when complex state or behavior differences exist between types.

Key Points:
- Enums are best for simple, constant lists.
- Sealed classes allow complex state and behavior.
- Sealed classes provide type safety and exhaustiveness in when expressions.

Example:

enum class Color { RED, GREEN, BLUE }

sealed class NetworkResponse
data class Success(val data: String) : NetworkResponse()
data class Error(val code: Int, val message: String) : NetworkResponse()

4. How can sealed classes be used in pattern matching with when expressions?

Answer: Sealed classes are particularly useful in pattern matching with when expressions because the compiler knows the exact types that could possibly inherit from the sealed class. This knowledge allows the compiler to enforce exhaustive checking in when expressions, ensuring that all possible cases are handled, which reduces runtime errors and increases code safety.

Key Points:
- Enforces exhaustive checking in when expressions.
- Enhances type safety and maintainability.
- Useful for implementing state machines and handling complex conditional logic.

Example:

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}