You are on page 1of 8

While building any application, we often need to create classes whose

primary purpose is to hold data/state. These classes generally contain


the same old boilerplate code in the form
of  getters ,  setters ,  equals() ,  hashcode()  and  toString()  meth
ods.

Motivation

Consider the following example of a  Customer  class in Java that just


holds data about a Customer and doesn’t have any functionality
whatsoever -

public class Customer {

private String id;

private String name;

public Customer(String id, String name) {

this.id = id;

this.name = name;

public String getId() {

return id;

public void setId(String id) {

this.id = id;

}
public String getName() {

return name;

public void setName(String name) {

this.name = name;

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Customer customer = (Customer) o;

if (id != null ? !id.equals(customer.id) : customer.id != null) return


false;

return name != null ? name.equals(customer.name) : customer.name ==


null;

@Override

public int hashCode() {

int result = id != null ? id.hashCode() : 0;

result = 31 * result + (name != null ? name.hashCode() : 0);

return result;

}
}

You see, for creating a Simple class with only two member fields, we
had to write almost 50 lines of code.
Yes, I know that you don’t need to write that code yourself and any good
IDE can generate all that boilerplate code for you.
But that code will still be there in your source file and clutter it. Moreover,
whenever you add a new member field to the Class, you’ll need to
regenerate/modify the constructors, getters/setters and
equals()/hashcode() methods.
You can also use a third party library like  Project Lombok  to
generate  getters/setters ,  equals()/hashCode() ,  toString()  m
ethods and more. But there is no out of the box solution without any
library that can help us avoid these boilerplate codes in our application.

Kotlin Data Classes

Kotlin has a better solution for classes that are used to hold data/state.
It’s called a Data Class. A Data Class is like a regular class but with
some additional functionalities.
With Kotlin’s data classes, you don’t need to write/generate all the
lengthy boilerplate code yourself. The compiler automatically generates
a default getter and setter for all the mutable properties, and a getter
(only) for all the read-only properties of the data class. Moreover, It also
derives the implementation of standard methods
like  equals() ,  hashCode()  and  toString()  from the properties
declared in the data class’s primary constructor.
For example, The  Customer  class that we wrote in the previous section
in Java can be written in Kotlin in just one line -
data class Customer(val id: Long, val name: String)

Accessing the properties of the data class


The following example shows how you can access the properties of the
data class -

val customer = Customer(1, "Sachin")

// Getting a property

val name = customer.name

Since all the properties of the  Customer  class are immutable, there is
no default setter generated by the compiler. Therefore, If you try to set a
property, the compiler will give an error -

// Setting a Property

// You cannot set read-only properties

customer.id = 2 // Error: Val cannot be assigned

Let’s now see how we can use the  equals() ,  hashCode() ,


and  toString()  methods of the data class-

1. Data class’s equals() method

val customer1 = Customer(1, "John")

val customer2 = Customer(1, "John")

println(customer1.equals(customer2)) // Prints true


You can also use Kotlin’s Structural equality operator  ==  to check for
equality. The  ==  operator internally calls the  equals()  method -

println(customer1 == customer2) // Prints true

2. Data class’s toString() method


The  toString()  method converts the object to a String in the form
of  "ClassName(field1=value1, field2=value)"  -

val customer = Customer(2, "Robert")

println("Customer Details : $customer")

# Output

Customer Details : Customer(id=2, name=Robert)

3. Data class’s hashCode() method

val customer = Customer(2, "Robert")

println("Customer HashCode : ${customer.hashCode()}") // Prints -1841845792

Apart from the standard methods


like  equals() ,  hashCode()  and  toString() , Kotlin also generates
a  copy()  function and  componentN()  functions for all the data
classes. Let’s understand what these functions do and how to use them
-

Data Classes and Immutability: The copy() function

Although the properties of a data class can be mutable (declared


using  var ), It’s strongly recommended to use immutable properties
(declared using  val ) so as to keep the instances of the data class
immutable.
Immutable objects are easier to work with and reason about while
working with multi-threaded applications. Since they can not be modified
after creation, you don’t need to worry about concurrency issues that
arise when multiple threads try to modify an object at the same time.
Kotlin makes working with immutable data objects easier by
automatically generating a  copy()  function for all the data classes. You
can use the  copy()  function to copy an existing object into a new object
and modify some of the properties while keeping the existing object
unchanged.
The following example shows how  copy()  function can be used -

val customer = Customer(3, "James")

/*

Copies the customer object into a separate Object and updates the name.

The existing customer object remains unchanged.

*/

val updatedCustomer = customer.copy(name = "James Altucher")

println("Customer : $customer")

println("Updated Customer : $updatedCustomer")

# Output

Customer : Customer(id=3, name=James)

Updated Customer : Customer(id=3, name=James Altucher)

Data Classes and Destructuring Declarations: The componentN()


functions
Kotlin also generates  componentN()  functions corresponding to all the
properties declared in the primary constructor of the data class.
For the  Customer  data class that we defined in the previous section,
Kotlin generates two  componentN()  functions
-  component1()  and  component2()  corresponding to
the  id  and  name  properties -

val customer = Customer(4, "Joseph")

println(customer.component1()) // Prints 4

println(customer.component2()) // Prints "Joseph"

The component functions enable us to use the so-called Destructuring


Declaration in Kotlin. The Destructuring declaration syntax helps you
destructure an object into a number of variables like this -

val customer = Customer(4, "Joseph")

// Destructuring Declaration

val (id, name) = customer

println("id = $id, name = $name") // Prints "id = 4, name = Joseph"

Requirements for Data Classes

Every Data Class in Kotlin needs to fulfill the following requirements -

 The primary constructor must have at least one parameter


 All the parameters declared in the primary constructor need to be
marked as  val  or  var .
 Data classes cannot be abstract, open, sealed or inner.
Conclusion

Data classes help us avoid a lot of common boilerplate code and make
the classes clean and concise. In this article, you learned how data
classes work and how to use them. I hope you understood the all the
concepts presented in this article.
Thank you for reading folks. See you in the next post!

You might also like