Kotlin Data Classes and Enums

Kotlin provides two specialized class types — data classes and enums — that solve common programming problems with very little code. Data classes make it easy to hold and compare data. Enums represent a fixed set of named values.

Data Classes

A data class is a class whose main purpose is to hold data. Kotlin automatically generates several useful methods for you: equals(), hashCode(), toString(), and copy().

Defining a Data Class

data class Student(
    val name: String,
    val rollNo: Int,
    val grade: String
)

What Kotlin Generates Automatically

val s1 = Student("Priya", 101, "A")
val s2 = Student("Priya", 101, "A")
val s3 = Student("Arun", 102, "B")

// toString() — readable output
println(s1)              // Student(name=Priya, rollNo=101, grade=A)

// equals() — compares content, not reference
println(s1 == s2)        // true  (same data)
println(s1 == s3)        // false (different data)

// copy() — create a modified copy
val updated = s1.copy(grade = "A+")
println(updated)         // Student(name=Priya, rollNo=101, grade=A+)
println(s1)              // Student(name=Priya, rollNo=101, grade=A) — unchanged

Diagram — Regular Class vs Data Class

Regular class — what you write AND what you must add manually:
class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?) = ...   // write yourself
    override fun hashCode() = ...            // write yourself
    override fun toString() = ...            // write yourself
    fun copy(...) = ...                      // write yourself
}

Data class — Kotlin writes all of that for you:
data class Point(val x: Int, val y: Int)

Destructuring Declarations

Data classes support destructuring — you can unpack multiple properties into separate variables in one line.

val student = Student("Neha", 103, "B+")
val (name, roll, grade) = student

println(name)    // Neha
println(roll)    // 103
println(grade)   // B+

// Useful in loops over a list of data objects
val students = listOf(
    Student("Kiran", 104, "A"),
    Student("Dev", 105, "B")
)
for ((n, r, g) in students) {
    println("$r: $n — Grade $g")
}

Enum Classes

An enum (short for enumeration) defines a type with a fixed set of possible values. Use enums when a variable can only be one of a known set of choices — like days of the week, app states, or directions.

Basic Enum

enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

val heading = Direction.NORTH
println(heading)          // NORTH
println(heading.name)     // NORTH (as String)
println(heading.ordinal)  // 0 (position in list, starts at 0)

Enum in when Expression

val move = Direction.EAST

when (move) {
    Direction.NORTH -> println("Moving up")
    Direction.SOUTH -> println("Moving down")
    Direction.EAST  -> println("Moving right")
    Direction.WEST  -> println("Moving left")
}
// Moving right

Enum with Properties and Methods

Enum constants can carry their own data and behavior.

enum class Planet(val massKg: Double, val radiusKm: Double) {
    MERCURY(3.30e23, 2440.0),
    VENUS  (4.87e24, 6052.0),
    EARTH  (5.97e24, 6371.0),
    MARS   (6.42e23, 3390.0);

    fun surfaceGravity(): Double {
        val G = 6.67e-11
        return G * massKg / (radiusKm * 1000) / (radiusKm * 1000)
    }
}

println(Planet.EARTH.surfaceGravity())   // ~9.8 m/s²
println(Planet.MARS.name)               // MARS

Diagram — Enum as a Sealed Set of Values

enum class Status {  PENDING, ACTIVE, SUSPENDED, CLOSED  }
                       │        │         │          │
                    allowed  allowed   allowed    allowed
                    values   values    values     values

val s: Status = Status.ACTIVE
s cannot be anything other than these four values.
This prevents typos and invalid states at compile time.

Iterating Over Enum Values

for (direction in Direction.values()) {
    println(direction)
}
// NORTH
// SOUTH
// EAST
// WEST

// Get enum from string
val d = Direction.valueOf("SOUTH")
println(d)   // SOUTH

Sealed Classes — Enums with Full Class Power

A sealed class is like an enum that allows each variant to carry different data. All subclasses must be in the same file. Use sealed classes to model result states like Success or Error.

sealed class ApiResult {
    data class Success(val data: String) : ApiResult()
    data class Error(val message: String, val code: Int) : ApiResult()
    object Loading : ApiResult()
}

fun handleResult(result: ApiResult) {
    when (result) {
        is ApiResult.Success -> println("Data: ${result.data}")
        is ApiResult.Error   -> println("Error ${result.code}: ${result.message}")
        is ApiResult.Loading -> println("Please wait...")
    }
}

handleResult(ApiResult.Success("User loaded"))
// Data: User loaded

handleResult(ApiResult.Error("Not found", 404))
// Error 404: Not found

Leave a Comment

Your email address will not be published. Required fields are marked *