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
-
Classes and Objects — ordinary classes and properties, which data classes build on.
-
Equality and Operator Overloading — what the generated
equals()actually checks, and==vs.===. -
Sealed Classes and Enums — pairing data classes with a
sealedhierarchy for exhaustivewhenmatching.