Data Classes

Data Classes and Structural Equality

Prefix a class with data and the compiler generates equals(), hashCode(), toString(), copy() and a componentN() per primary-constructor property. The constructor needs at least one parameter, each val or var, and a data class cannot be abstract, open, sealed or inner. Here is BookNest's Book beside a plain class:

An ordinary class and a data class compared (data.kt)Kotlin
class PlainBook(val id: Int, val title: String)
data class Book(val id: Int, val title: String, val author: String, val price: Double) {
  var notes = 0   // declared in the body: ignored by equals, hashCode and toString
}
fun main() {
  println(PlainBook(1, "The Quiet Harbor") == PlainBook(1, "The Quiet Harbor"))
  val a = Book(1, "The Quiet Harbor", "Mara Ellison", 14.99)
  val b = Book(1, "The Quiet Harbor", "Mara Ellison", 14.99).apply { notes = 7 }
  println(a)
  println("${a == b} ${a === b} ${a.hashCode() == b.hashCode()} ${setOf(a, b).size}")
}
Output
false
Book(id=1, title=The Quiet Harbor, author=Mara Ellison, price=14.99)
true false true 1

== calls equals(); === tests identity. PlainBook inherits identity equals() from Any. The data class compares constructor properties only, so a == b despite different notes, and the set drops the duplicate. Compose 234 skips a composable whose stable parameters are unchanged, and StateFlow (StateFlow and SharedFlow) drops a value that equals the current one, so keep constructor properties val: mutating one also changes the hash of an object already in a HashSet.