Kotlin Data Class

In this article, you will learn to create data classes in Kotlin. You will also learn about requirements that data class must fulfill, and their standard functionalities.

There may arise a situation where you need to create a class solely to hold data. In such cases, you can mark the class as data to create a data class. For example,

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

For this class, the compiler automatically generates:

  • copy() function, equals() and hashCode() pair, and toString() form of the primary constructor
  • componentN() functions

Before talking about these features in detail, let's talk about requirements that a data class must fulfill.


Kotlin Data Class Requirements

Here are the requirements:

  • The primary constructor must have at least one parameter.
  • The parameters of the primary constructor must be marked as either val (read-only) or var (read-write).
  • The class cannot be open, abstract, inner or sealed.
  • The class may extend other classes or implement interfaces. If you are using Kotlin version before 1.1, the class can only implement interfaces.

Example: Kotlin Data Class

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

fun main(args: Array<String>) {
    val jack = User("jack", 29)
    println("name = ${jack.name}")
    println("age = ${jack.age}")
}

When you run the program, the output will be:

name = jack
age = 29

When you declare a data class, the compiler automatically generates several functions such as toString(), equals(), hashcode() etc behind the scenes. This helps to keep you code concise. Had you used Java, you would need to write a lot of boilerplate code.

Let's use these functions:


Copying

For a data class, you can create a copy of an object with some of its properties different using copy() function. Here's how it works:

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

fun main(args: Array<String>) {
    val u1 = User("John", 29)
   
    // using copy function to create an object
    val u2 = u1.copy(name = "Randy")

    println("u1: name = ${u1.name}, name = ${u1.age}")
    println("u2: name = ${u2.name}, name = ${u2.age}")
}

When you run the program, the output will be:

u1: name = John, name = 29
u2: name = Randy, name = 29

toString() method

The toString() function returns a string representation of the object.

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

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    println(u1.toString())
}

When you run the program, the output will be:

User(name=John, age=29)

hashCode() and equals()

The hasCode() method returns hash code for the object. If two objects are equal, hashCode() produces the same integer result. Recommended Reading: hashCode()

The equals() returns true if two objects are equal (has same hashCode()). If objects are not equal, equals() returns false. Recommended Reading: equals()

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

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    val u2 = u1.copy()
    val u3 = u1.copy(name = "Amanda")

    println("u1 hashcode = ${u1.hashCode()}")
    println("u2 hashcode = ${u2.hashCode()}")
    println("u3 hashcode = ${u3.hashCode()}")

    if (u1.equals(u2) == true)
        println("u1 is equal to u2.")
    else
        println("u1 is not equal to u2.")

    if (u1.equals(u3) == true)
        println("u1 is equal to u3.")
    else
        println("u1 is not equal to u3.")
}

When you run the program, the output will be:

u1 hashcode = 71750738
u2 hashcode = 71750738
u3 hashcode = 771732263
u1 is equal to u2.
u1 is not equal to u3.

Destructuring Declarations

You can destructure an object into a number of variables using destructing declaration. For example:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    val (name, age, gender) = u1
    println("name = $name")
    println("age = $age")
    println("gender = $gender")
}

When you run the program, the output will be:

name = John
age = 29
gender = Male

This was possible because the compiler generates componentN() functions all properties for a data class. For example:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    println(u1.component1())     // John
    println(u1.component2())     // 29  
    println(u1.component3())     // "Male"
}

When you run the program, the output will be:

John
29
Male