Kotlin Data Classes and Sealed Classes: Features, Copy, Destructuring & Type-Safe Hierarchies

1. Data Classes

What are data classes in Kotlin?

Data classes are special classes designed to hold data, automatically providing implementations for common methods like toString(), equals(), hashCode(), and copy().

Syntax: Declared with the data keyword: data class ClassName(val/var property: Type).

Features:

Requirements: Must have at least one primary constructor parameter with val or var.

Use Case: Representing data models (e.g., user profiles, API responses).

Limitation: Cannot be abstract, open, sealed, or inner.

Example:

// Data class example
data class User(val id: Int, val name: String, val email: String)

fun main() {
    // Create instances
    val user1 = User(1, "Krishna", "[email protected]")
    val user2 = User(1, "Krishna", "[email protected]")
    val user3 = user1.copy(email = "[email protected]")

    // toString()
    println("User1: $user1")
    
    // equals()
    println("User1 == User2: ${user1 == user2}")
    
    // copy()
    println("User3 (copied): $user3")
    
    // Destructuring
    val (id, name, email) = user1
    println("Destructured: id=$id, name=$name, email=$email")
}
Output:
User1: User(id=1, name=Krishna, [email protected])
User1 == User2: true
User3 (copied): User(id=1, name=Krishna, [email protected])
Destructured: id=1, name=Krishna, [email protected]

Note: User auto-generates toString(), equals(), hashCode(), and copy(). copy() creates a new instance with modified properties. Destructuring allows accessing properties via componentN() functions.

2. Sealed Classes

What are sealed classes in Kotlin?

Sealed classes are a restricted class hierarchy where all subclasses must be defined within the same file or module (in Kotlin 1.5+).

Syntax: Declared with the sealed keyword: sealed class ClassName.

Features:

Subclasses: Can be data class, object, or regular class within the sealed hierarchy.

Use Case: Modeling finite states (e.g., success/error/loading, algebraic data types).

Limitation: Subclasses must be in the same file (pre-Kotlin 1.5) or module (Kotlin 1.5+).

Example:

// Sealed class example
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

fun processResult(result: Result): String {
    return when (result) {
        is Result.Success -> "Success: ${result.data}"
        is Result.Error -> "Error: ${result.message}"
        is Result.Loading -> "Loading..."
    }
}

fun main() {
    val success = Result.Success("Data loaded")
    val error = Result.Error("Failed to load")
    val loading = Result.Loading

    println(processResult(success))
    println(processResult(error))
    println(processResult(loading))
}
Output:
Success: Data loaded
Error: Failed to load
Loading...

Note: Result is a sealed class with three subclasses: Success, Error, and Loading. when expression ensures all cases are handled, leveraging type safety. Subclasses can be data classes (Success, Error) or objects (Loading).

3. Comprehensive Example Combining Data Classes and Sealed Classes

Example:

// Comprehensive example with data classes and sealed classes
data class User(val id: Int, val name: String, val email: String)

sealed class UserOperation {
    data class Created(val user: User) : UserOperation()
    data class Updated(val user: User) : UserOperation()
    data class Deleted(val userId: Int) : UserOperation()
    object Fetching : UserOperation()
}

fun processOperation(operation: UserOperation): String {
    return when (operation) {
        is UserOperation.Created -> {
            val (id, name, email) = operation.user
            "Created user: id=$id, name=$name, email=$email"
        }
        is UserOperation.Updated -> "Updated user: ${operation.user}"
        is UserOperation.Deleted -> "Deleted user with id: ${operation.userId}"
        is UserOperation.Fetching -> "Fetching user..."
    }
}

fun main() {
    // Create data class instances
    val user1 = User(1, "Krishna", "[email protected]")
    val user2 = user1.copy(id = 2, name = "Ram")

    // Create sealed class instances
    val operations = listOf(
        UserOperation.Created(user1),
        UserOperation.Updated(user2),
        UserOperation.Deleted(1),
        UserOperation.Fetching
    )

    // Process operations
    operations.forEach { operation ->
        println(processOperation(operation))
    }

    // Demonstrate equality and copy
    println("\nUser1 == User2: ${user1 == user2}")
    println("User1 copy: ${user1.copy(email = "[email protected]")}")
}
Output:
Created user: id=1, name=Krishna, [email protected]
Updated user: User(id=2, name=Ram, [email protected])
Deleted user with id: 1
Fetching user...

User1 == User2: false
User1 copy: User(id=1, name=Krishna, [email protected])

Description: Data Class: User provides toString(), equals(), copy(), and destructuring. Sealed Class: UserOperation models user-related operations with subclasses. Integration: when processes UserOperation instances, leveraging User properties. Demonstrates type safety and concise data handling.

4. Common Mistakes and Best Practices

Common Mistakes:

Best Practices: