Kotlin Data Classes

The main purpose of many classes is to hold data. But to effectively manipulative with data, we need to override quite a lot data. Class holding 3 String values would typically look something like this in Java:

public class User {
    private String username;
    private String firstname;
    private String lastname;
    public User(String username, String firstname, String lastname) {
        this.username = username;
        this.firstname = firstname;
        this.lastname = lastname;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public String getFirstname() {
        return firstname;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    public String getLastname() {
        return lastname;
    }
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (obj.getClass() != getClass()) return false;
        User otherUser = (User) obj;
        return otherUser.username.equals(username)
                && otherUser.firstname.equals(firstname)
                && otherUser.lastname.equals(lastname);
    }
    @Override
    public int hashCode() {
        int randomNumber = 42;
        int result = 1;
        result = randomNumber * result + (username == null ? 0 : username.hashCode());
        result = randomNumber * result + (firstname == null ? 0 : firstname.hashCode());
        result = randomNumber * result + (lastname == null ? 0 : lastname.hashCode());
        return result;
    }
    @Override
    public String toString() {
        return "Username:" + username + ", firstname:" + firstname + ", lastname:" + lastname;
    }

We need to override methods equals and hashCode methods to make sure that object equality will be properly determined. If we want to get meaningful object String representation, we also need to override method toString. Otherwise we only get object memory address that won‘ t tell us much. Something like following:

User@249c6ca

That is an impressive number of lines for such simple class. Fortunately, we can use Kotlin to make things simpler. This class would look like this in Kotlin:

data class User(var username: String, var firstname: String, var lastname: String)

Data class is initiated same way as any other Kotlin class.

val user = User("john1", "John", "First")

One line. Wow. So what does Kotlin do with data class?

Data class is Kotlin class that implements all Java example’s counterpart functionality and more. For data class Kotlin automatically generates field accessors, equals, hashCode and toString functions. And also it creates componentN functions (where N stands for 1st to Nth argument) and copy function.

Component N functions

These functions enable us to deconstruct object properties as individual values. For data class property Kotlin generates componentN function.

So in our example user.component1() returns first property – username, user.component2() returns second property – firstname and so on. We can even get multiple properties at once.

val (username, firstname, lastname) = user2

This can be useful for example inside loops:

val usersList = listOf<User>(user, user2, user3, user4)
for ((username, firstname, lastname) in usersList) {
    println("User $username has last name $lastname")
}

Or when dealing with maps:

for ((key, value) in usermap) {
    println("$key: $value")
}

Copy function

Making a copy of an object in Java that won‘t be affected by changes to original object is not easy. It is a quite complicated process and I will not write about it in this post. You can check this topic on Stackoverflow. As usual, it is also very simple in Kotlin.

Just call copy function.

val user4 = user.copy()

Now you have 2 completely independent objects. If you want to copy object and change some property, put it in parameters:

val user4 = user.copy(lastName = "Fourth")

Rules

So should you create all your classes holding some data as data classes? Not so fast! There are some rules that need to be followed and some of them are fairly strict.

  • The primary constructor needs to have at least one parameter;
  • All primary constructor parameters need to be marked as val or var;
  • Data classes cannot be abstract, open, sealed or inner;
  • (before 1.1) Data classes may only implement interfaces.