Chapter 7 of 12 ~12 min read

Data Classes

Replace Java POJOs with a single line. Data classes auto-generate equals, hashCode, toString, copy, and destructuring.


Java POJO vs Kotlin Data Class

Java — 40+ lines
public class User {
    private String name;
    private String email;
    private int age;

    public User(String name,
                String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public String getName()  { return name; }
    public String getEmail() { return email; }
    public int getAge()      { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User u = (User) o;
        return age == u.age &&
            Objects.equals(name, u.name) &&
            Objects.equals(email, u.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email, age);
    }

    @Override
    public String toString() {
        return "User{name=" + name +
            ", email=" + email +
            ", age=" + age + "}";
    }
}
Kotlin — 1 line
data class User(
    val name: String,
    val email: String,
    val age: Int
)

// ✅ Automatically generated:
// equals() — structural equality
// hashCode() — consistent with equals
// toString() — User(name=Alice, ...)
// copy() — create modified copies
// componentN() — for destructuring
Biggest boilerplate win in Kotlin

Data classes are one of the most beloved Kotlin features for Java developers. The 40-line Java POJO becomes a single line without losing any functionality.

The copy() Function

Create a new instance based on an existing one with some fields changed — perfect for immutable state updates:

Kotlin
val alice = User(name = "Alice", email = "alice@ex.com", age = 30)

// Create a modified copy — original unchanged
val olderAlice = alice.copy(age = 31)
val renamed = alice.copy(name = "Alicia", email = "alicia@ex.com")

// In Android ViewModels — immutable state updates:
_uiState.value = _uiState.value.copy(isLoading = false)

Destructuring Declarations

Java
User user = getUser();
String name  = user.getName();
String email = user.getEmail();
int age      = user.getAge();
Kotlin
val user = getUser()
val (name, email, age) = user  // destructuring!

// Also works in for loops:
val users = listOf(User("Alice", "a@b.com", 30))
for ((name, email) in users) {
    println("$name - $email")
}

Data Class Limitations

Important limitations
  • Cannot be abstract, open, sealed, or inner
  • Only primary constructor parameters participate in generated functions
  • Properties declared in the body are NOT included in equals/hashCode/toString
  • Should represent pure data — avoid business logic in data classes

Structural Equality

Java
User a = new User("Alice", "a@b.com", 30);
User b = new User("Alice", "a@b.com", 30);

// == checks reference
a == b       // false (different objects!)
a.equals(b)  // true (if equals() overridden)
Kotlin
val a = User("Alice", "a@b.com", 30)
val b = User("Alice", "a@b.com", 30)

// == calls equals() — structural!
a == b   // ✅ true
a === b  // false (referential — like Java ==)