Skip to content

Kotlin Primer

Data Classes

Kotlin has many shortcuts, and one of my favorites is data class. Data classes automatically create the following functions in the class

  • toString() with all primary-constructor parameters (in order)
  • equals()/hashCode()
  • copy() to create a copy of the object with possible property overrides
  • componentN() for use in destructuring declarations

Note

Data classes can have additional properties inside the class definition, as well as functions, nested types, etc. Specifying data class just adds above functions for you.

toString()

Note

The toString() function in Java and Kotlin is intended as a debugging aid, not as a way to format data for a user! Output formatting for user display should be a function of your user interface, not your data definitions.

If we use a non-data class, the toString() is inherited from Any (the superclass of all classes), which just prints the type name and hash code

class Person(
    val name: String,
    val age: Int,
)

fun main() {
    val scott = Person("Scott", 59)
    println(scott) // calls scott.toString()
}

Making the class a data class adds a nicely-readable dump of the type name and all primary-constructor parameters. Note that member properties will not appear in the output!

data class Person(
    val name: String,
    val age: Int,
) {
    var someData: Int = 0 // won't be printed!
}

fun main() {
    val scott = Person("Scott", 59)
    println(scott) // calls scott.toString()
}

equals() and hashCode()

These functions serve the same purpose as they do in Java, for value comparison and hashCode generation (most-often used when using the object as a key in a hash map). They will use the values and hash codes of properties defined in the primary constructor.

data class Person(
    val name: String,
    val age: Int,
) {
    var someData: Int = 0 // won't be used in equals/hashCode
}

fun main() {
    val scott = Person("Scott", 59)
    val mikey = Person("Mikey", 10)
    println(mikey == scott) // calls mikey.equals(scott)
}

copy()

The copy function is a simple way to copy all the primary-constructor parameter properties while allowing individual properties to be changed.

data class Person(
    val name: String,
    val age: Int,
)

fun main() {
    val scott = Person("Scott", 59)
    val youngScott = scott.copy(age = 10)
        // all properties are copied, except for those specified
    println(youngScott)
}

In this example, the copy() function is basically

data class Person(
    val name: String,
    val age: Int,
) {
    fun copy(
        val name: String = this.name,
        val age: Int = this.age,
    ) = Person(name, age)
}

componentN()

A destructuring declaration allows you to create multiple properties at once, initializing them to values pulled from componentN() functions. If you define a non-data class, you can create any number of componentN() functions, starting with N=1, such as component1()

class Person(
    val name: String,
    val age: Int,
) {
    operator fun component1() = name
    operator fun component2() = age
}

fun main() {
    val scott = Person("Scott", 59)

    // destructuring declaration
    // calls component1() and component2() to get
    //   values in order and assigns
    // component1, component2, etc are defined by order
    //   of parameters in primary constructor
    val (name, age) = scott
    println(name)
    println(age)
}

These do not need to return property values; they could return constants or derived values!

When you define a data class, componentN() functions are automatically generated for each primary-constructor property, in order of definition.

data class Person(
    val name: String,
    val age: Int,
)

fun main() {
    val scott = Person("Scott", 59)

    // destructuring declaration
    // calls component1 and component2 to get
    //   values in order and assigns
    // component1, component2, etc are defined by order
    //   of parameters in primary constructor
    val (name, age) = scott
    println(name)
    println(age)
}

Data Objects

You can also specify data object to add a nice base toString() that just returns the name of the class (without its hashCode() appended). The other functisonality is not added.