Data Classes and Destructuring

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A data class marks a class whose entire purpose is holding values, and the compiler generates the boilerplate every Java IDE would otherwise auto-generate by hand.

Declaring a Data Class

data class Point(val x: Int, val y: Int)

val p1 = Point(1, 2)
val p2 = Point(1, 2)

println(p1)               // Point(x=1, y=2)             -- generated toString()
println(p1 == p2)          // true                        -- generated equals(), structural comparison
println(p1.hashCode() == p2.hashCode())  // true            -- generated hashCode(), consistent with equals()

The compiler generates equals()/hashCode() (based on every property declared in the primary constructor), toString(), copy(), and the componentN() functions below — from one declaration, versus Java’s alternative of either hand-writing all of it or reaching for a record (which covers the same ground but without copy() or mutable properties).

copy()

copy() returns a new instance with the same property values, except for whichever named arguments are supplied — the standard way to produce a modified value from an otherwise-immutable data class without a mutation:

val original = Point(1, 2)
val moved = original.copy(y = 20)     // Point(x=1, y=20) -- x is carried over unchanged

componentN() and Destructuring Declarations

Each constructor property also gets a componentN() function (component1(), component2(), …​), which is what lets a data class be destructured into several variables in one statement:

val (x, y) = Point(3, 4)     // calls component1()/component2() under the hood
println("$x, $y")             // 3, 4

Destructuring is not limited to data classes — Map.Entry and Pair/Triple (from kotlin.to/the stdlib) support it out of the box, and any class can opt in by defining its own componentN() operator functions. It shows up most often in two places:

val scores = mapOf("Ada" to 95, "Grace" to 98)
for ((name, score) in scores) {          // destructuring in a for loop over Map.Entry
    println("$name: $score")
}

scores.forEach { (name, score) ->        // destructuring a Pair-like parameter in a lambda
    println("$name scored $score")
}

val (min, max) = listOf(3, 1, 4, 1, 5).let { it.min() to it.max() }   // destructuring a Pair

An unused destructured component can be named to signal it is intentionally ignored, e.g. val (, score) in scores.

See Also