Kotlin Data Class: When and How to Use Data Class in Kotlin

This Kotlin Data Class tutorial explains when and how to use data class in Kotlin and different features like Syntax, Constructor, Operations, etc:

Kotlin provides a great feature through data classes in order to eliminate a lot of boilerplate code like constructors, getter/setters, toString method, etc that you would usually write in order to achieve the same functionality in various other programming languages like Java, C#, etc.

In this tutorial, we will see a comparison of how you can use data classes in Kotlin to reduce a lot of code into merely a couple of lines with comparison examples from popular programming languages like Java.

=> Read ALL Our Kotlin Tutorials Here

Kotlin Data Class

Kotlin Data Class

When to Use Data Class

A lot of times in the applications you would need classes solely for the purpose of storing data in the objects and you would use those objects to set/retrieve values of corresponding properties. (What this also means is for these classes, you won’t have a lot of member functions as a part of the class). We can use Data Classes in such cases.

Syntax

data class {className} (val dataMember1 : DataType1, val dataMember2: DataType2)

Example: Let’s see an example of an Employee data class – having parameters as

  • empName (String)
  • empAge (Integer)
  • empId (Integer)
  • empCity (String)
data class Employee(
val empName: String,
val empId: Int,
val empAge: Int,
val empCity: String
)

As you can see above, we have created a data class named ‘Employee’ with 4 parameters namely – empName, empId, empAge, and empCity.

In the further sections, we will learn how to instantiate and use the data classes and what are the different operations that can be performed with these.

Kotlin Data Class Requirements

In order to define a class as a data class in Kotlin, it should satisfy the requirements below:

  • The primary constructor should have at least one parameter.
  • Data class cannot be abstract or sealed.
  • Data class can implement interfaces and extend to other classes.
  • The parameters of the class can be either val or var type.

Data Class Kotlin – Operations

Creating an Instance of Data class

Creating a Data class instance is similar to what you would do for any other class. (Please note that you would not be able to reassign the parameters declared as ‘val’ during class declaration – If the values need to be changed they need to be defined as parameters of type ‘var’)

val employee = Employee("Adam Baer", 123, 45, "Norway")

We can also instantiate the class using named parameters (if we don’t want to specify parameters in order, or we just want to instantiate with a subset of the parameters in the constructor)

Suppose we want to create an employee object with just name, id, and age, then we can use the below declaration.

var empSubset = Employee(empName = "Adam Baer", empId = 111, empAge = 26)

However, for the above to work, you need to ensure that there are default values available for the parameters which are not being defined.

data class Employee(
val empName: String,
val empId: Int,
val empAge: Int,
val empCity: String = ""
)

As you can see above, we have provided an empty string as the default value for empCity in the data class constructor.

Data Class toString(), equals() and hashCode() function

Data class in Kotlin provide default implementation of toString(), equals() and hashCode() functions without explicit declaration.

Let’s understand these concepts with the help of an example.

  • toString() – The default implementation of the toString() method would return the output in the following format:
    “ObjectType(parameter1=value1, parameter2=value2…)”

Let’s try this with an example:

val employee = Employee("Adam Baer", 123, 45, "Norway")
println(employee.toString())

//Output
Employee(empName=Adam Baer, empId=123, empAge=45, empCity=Norway)
  • equals() – equals is used to compare 2 objects of the same type – the comparison would result true, if all parameters defined in the constructor have equal value.
    val employee = Employee("Adam Baer", 123, 45, "Norway")
    val employee1 = Employee("Adam Baer", 123, 45, "Norway")
    println("Employee1 == employee = > ${employee.equals(employee1)}")
    
    //Output
    Employee1 == employee = > true
  • hashCode() – hashCode() function returns an Integer value that’s generated by some defined hashing algorithm.

Let’s use the above example to generate hashCode for an Employee object:

val employee = Employee("Adam Baer", 123, 45, "Norway")
println("Hash code for employee - ${employee.hashCode()}")

//Output
Hash code for employee - -1533042289

It’s also important to note that hashCode() function would

  • Generate the same code/integer value when it’s called one or more times on the same object.
  • If the equals() method returns true for 2 objects, the hashCode() function would return the same integer value for both the objects.

Let’s try generating a hashCode for 2 equal objects:

 // illustrating equals() method
val employee = Employee("Adam Baer", 123, 45, "Norway")
val employee1 = Employee("Adam Baer", 123, 45, "Norway")
println("Employee1 == employee = > ${employee.equals(employee1)}")
println("Hash code for employee - ${employee.hashCode()}")
println("Hash code for employee1 - ${employee1.hashCode()}")

//Output
Employee1 == employee = > true
Hash code for employee - -1533042289
Hash code for employee1 - -1533042289

Properties Defined in Constructor Vs Class Body

Kotlin default functions like toString(), equals() apply to properties declared only in the constructor and not within the class body.

Example for a data class implementation like below:

data class Student(val name: String) {
var age: Int = 0
}

// if we execute below code
val student = Student("Abhishek Kapoor")
student.age = 20
println(student.toString())

//Output
Student(name=Abhishek Kapoor)

As you can see in the example above – the toString() method has printed just the property “name” and not “age” – Similar to toString(), other methods like equals(), hashCode(), etc also apply to parameters that are a part of the constructor body only.

In order to use the parameters defined in the class body, you will need to override these methods and use the parameters accordingly.

Let’s learn to override the default implementation in the next section.

Overriding Default Implementation of Methods like – toString(), equals()

Let’s now see how we can override the default implementation of the methods that come along with the data class. We will override the toString() method:

data class Student(val name: String, val age: Int) {
override fun toString(): String {
return ("$name : $age")
}
}

val student = Student("Abhishek Kapoor", 20)
println(student.toString())

//Output
Abhishek Kapoor : 20

As you can see above, we have overridden toString() method and displayed a custom output when the classObject.toString() method is invoked.

Visibility Modifiers in Data Class

The visibility of parameters can be controlled for a data class by using different combinations of the available modifiers.

For example: For read-only fields, we can have parameters declared as val – which would not allow reassignment, but would allow retrieving the value as getters.

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

Using private fields, we can restrict usage outside the class

data class Employee(val name: String, private val salary: Int)

As you can see above, we have declared salary as a private field i.e. it can’t be retrieved from the object.

Copying Data Class Objects

Kotlin data class also exposes copy() method default implementation. When executed, it creates a copy of all the individual parameters for the given object.

val employee = Employee("Adam Baer", 123, 45, "Norway")
val empCopy = employee.copy()
println("Original: ${employee.toString()} || Copied: ${empCopy.toString()}")

//Output
Original: Employee(empName=Adam Baer, empId=123, 
empAge=45, empCity=Norway) || Copied: Employee(empName=Adam Baer, empId=123, empAge=45, empCity=Norway)

Destructuring

Destructuring allows us to break a data class in Kotlin into individual fields. For each specified parameter or property, Kotlin generates the componentN() function which maps to each property.

val employee = Employee("Adam Baer", 123, 45, "Norway")
println("Components: ${employee.component1()} | ${employee.component2()} | ${employee.component3()} | 
${employee.component4()}")

//Output
Components: Adam Baer | 123 | 45 | Norway

This is generally helpful while iterating over a Collection or Map of objects from the Data class, where we can directly retrieve one or more properties of a data class object.

Implementing Interface

Data classes don’t play very well with inheritance i.e extending from other data classes, but they do allow implementing an interface.

Given below is an example:

Suppose we have an interface, having a field to capture hobbies for a person:

interface EmpHobbies {
val hobbies: List<String>
}

Now, let’s see how we can implement this with our employee class:

// data class implementing interface
data class EmpInterface(val name: String, private val salary: Int, override val hobbies: List<String>) : EmpHobbies

// object declaration - including interface properties
val empInterface = EmpInterface("Shantanu", 100, listOf("Swimming","Dancing"))
println(empInterface.toString())

//Output
EmpInterface(name=Shantanu, salary=100, hobbies=[Swimming, Dancing])

Comparison with Java

Let’s now try to understand how we would achieve the same functionality in other languages like Java.

We have learned that data classes are a great means to reduce a lot of boilerplate code, let’s understand this with the help of what it would take to do similar stuff in Java.

To declare an Employee POJO with 2 properties – empName and empId, we would have to do the below:

public class Employee {
private String empName;
private int empId = 0;

public Employee(String empName, int empId) {
this.empName = empName;
this.empId = empId;
}

public String getEmpName() {
return empName;
}

public void setEmpName(String empName) {
this.empName = empName;
}

public int getEmpId() {
return empId;
}

public void setEmpId(int empId) {
this.empId = empId;
}

@Override
public boolean equals(Object o) {
// override equals
}

@Override
public int hashCode() {
//override hashcode
}

@Override
public String toString() {
// override toString
}
}

In Kotlin, you can achieve above 40-50 lines of Java code in just 1 line as below:

data class Employee(val empName: String, var empId: Int = 0)

In the example above, you can clearly see that in Kotlin, we considerably reduce the repeated boilerplate code and focus on shorthand declaration with a default implementation for useful functions as well as no explicit need to create getters/setters.

Difference Between Data Class and Class

In Kotlin, we can create both a normal class as well as a data class.

Let’s see some differences between these 2:

Data classClass
Contains only state and doesn’t perform any operationSuitable for objects which require to store state as well as require member functions to manipulate data or perform business logic.
Auto generates boiler plate code like - getter / setter, functions like - hashCode(), toString() etcNeed to be explicitly defined
SyntaxSyntax
data class className([parameter list])class className {
// data members
// member functions
}

Frequently Asked Questions

Q #1) What is a data class in Kotlin?

Answer: Kotlin provides a special type of class called data class, which is usually used for objects that act as a store for data properties and has no business logic or member functions. It provides a lot of advantages with reduced boilerplate code.

Q #2) How do I set data in a data class called Kotlin?

Answer: Data class can have parameters of type var or val depending on whether we want a parameter to be able to be reassigned or be used as a constant. For either type of variable, we can set the default values along with the declaration.

As you can see below, we are setting the default value of empCity = New York

data class Employee(val empName: String, val empId: Int, val empAge: Int, var empCity: String = "New York")

In order to assign values during object creation – we can simply assign the values as per the constructor of the data class.

fun main() {
var employee = Employee("Paul Mitchell", 123, 23, "London")
}

Q #3) Are Kotlin data classes immutable?

Answer: Kotlin defines immutability by using the val keyword. If all the parameters in your data class are of type “val” you could assume your class to be immutable as no parameters could be re-assigned. In other words, they are treated as read-only variables (or similar to final variables in programming languages like Java).

Q  #4) Can a Kotlin data class have methods?

Answer: There is no stopping if someone wants to add methods inside a data class. In fact, data classes are just like normal classes with some exceptions like having a primary constructor with at least 1 parameter, and it can’t be abstract or sealed.

Let’s see an example with the Student data class having a member function named hello – which would be called by the object of the Student class.

data class Student(val name: String, val age: Int) {
override fun toString(): String {
return ("$name : $age")
}
fun hello() {
println("hello ${this.name}")
}
}

// main function to execute data class function
fun main() {
var student = Student("Josh", 19)
student.hello()
}

//Output
hello Josh

Conclusion

In this tutorial, we learned how we can use data classes in Kotlin, and the different features that come along with these classes. They immensely reduce the writing of boilerplate code like standard function implementation – e.g. copy(), equals() and implementation of getter/setters without explicit need.

Data classes are widely used for the purpose of declaring objects that are just meant to store data without a lot of functions/business logic implementations for these. These can be directly compared with the way we have POJO’s in Java world and POCOs in C# world. To learn more about Data Classes please visit Kotlin Website.

PREV Tutorial | NEXT Tutorial