Kotlin Classes and Data Classes

Kotlin classes are concise and powerful. Data classes, sealed classes, and extension functions reduce boilerplate dramatically.

Basic Classes

Kotlin
class User(val name: String, var age: Int) {
    // Primary constructor parameters with val/var become properties
    
    init {
        require(age >= 0) { "Age cannot be negative" }
    }
    
    // Secondary constructor
    constructor(name: String) : this(name, 0)
    
    // Property
    val isAdult: Boolean
        get() = age >= 18
    
    // Method
    fun greet() = "Hi, I'm $name!"
}

val user = User("Alice", 28)
println(user.name)      // Alice
println(user.isAdult)   // true
user.age = 29           // Mutable
// user.name = "Bob"    // COMPILE ERROR (val)

Data Classes

Kotlin
// Auto-generates: equals, hashCode, toString, copy, componentN
data class Point(val x: Double, val y: Double)
data class User(val name: String, val email: String, val age: Int)

val p1 = Point(1.0, 2.0)
val p2 = Point(1.0, 2.0)
p1 == p2                     // true (structural equality)
p1.toString()                // "Point(x=1.0, y=2.0)"

// Copy with modifications
val alice = User("Alice", "alice@test.com", 28)
val olderAlice = alice.copy(age = 29)

// Destructuring
val (name, email, age) = alice
println("$name is $age years old")

// Data class with defaults
data class Config(
    val host: String = "localhost",
    val port: Int = 8080,
    val debug: Boolean = false
)
Config(debug = true)  // host=localhost, port=8080, debug=true

Sealed Classes

Kotlin
// Restricted class hierarchy — all subclasses in same file
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

// Exhaustive when expression (no else needed)
fun <T> handleResult(result: Result<T>): String = when (result) {
    is Result.Success -> "Got data: ${result.data}"
    is Result.Error -> "Error: ${result.exception.message}"
    is Result.Loading -> "Please wait..."
}

// sealed interface (Kotlin 1.5+)
sealed interface Shape {
    data class Circle(val radius: Double) : Shape
    data class Rectangle(val width: Double, val height: Double) : Shape
}

fun area(shape: Shape): Double = when (shape) {
    is Shape.Circle -> Math.PI * shape.radius * shape.radius
    is Shape.Rectangle -> shape.width * shape.height
}

Enum Classes

Kotlin
enum class Color(val hex: String) {
    RED("#ff0000"),
    GREEN("#00ff00"),
    BLUE("#0000ff");
    
    fun rgb() = hex.removePrefix("#").toInt(16)
}

val red = Color.RED
println(red.hex)       // #ff0000
println(red.rgb())     // 16711680

// When with enum
fun describe(color: Color) = when (color) {
    Color.RED -> "Warm"
    Color.GREEN -> "Natural"
    Color.BLUE -> "Cool"
}

Interfaces

Kotlin
interface Drawable {
    fun draw()
    
    // Default implementation
    fun highlight() = println("Highlighted!")
}

interface Resizable {
    val minWidth: Int
    fun resize(factor: Double)
}

class Button(val label: String) : Drawable, Resizable {
    override val minWidth = 100
    
    override fun draw() = println("Drawing button: $label")
    override fun resize(factor: Double) = println("Resizing by $factor")
}

Extension Functions

Kotlin
// Add methods to existing classes without modifying them
fun String.isPalindrome(): Boolean {
    val cleaned = lowercase().filter { it.isLetterOrDigit() }
    return cleaned == cleaned.reversed()
}

"racecar".isPalindrome()  // true
"hello".isPalindrome()    // false

// Extension properties
val String.wordCount: Int
    get() = split("\\s+".toRegex()).size

"Hello World".wordCount  // 2

// Nullable extensions
fun String?.orDefault(default: String = "N/A"): String {
    return this ?: default
}

val name: String? = null
name.orDefault()  // "N/A"

💡 Tip: Use data classes for anything that represents data. Use sealed classes for representing states or variants. Use extension functions to add utility methods cleanly.

Next: Kotlin Functions and Lambdas