Classes & Data Classes
🏗️ Classes & Data Classes
🧱 The Primary Constructor
Kotlin classes declare their constructor parameters directly in the class header, and can turn them into properties with a single keyword:
val and var inside the constructor parentheses do two things at once: declare the parameter and promote it to a property of the class, automatically generating a getter (and a setter, for var). There's no separate field declaration, no this.name = name assignment, and no hand-written getter/setter — all of that Java ceremony collapses into the single constructor line.
A Simple Class: Java vs Kotlin
| Java | Kotlin |
|---|---|
| public class Person { private final String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } | class Person(val name: String, var age: Int) |
⚙️ Init Blocks & Body Properties
Logic that needs to run at construction time goes in an init block; properties that aren't simple constructor parameters are declared in the class body:
require(...) throws an IllegalArgumentException if the condition is false — Kotlin's built-in, one-line way of validating constructor arguments, instead of writing an explicit if and throw.
📋 Data Classes
Adding the data keyword to a class generates equals(), hashCode(), toString(), and copy() automatically, based on the constructor properties:
toString() — Free Debug Output
A plain Kotlin (or Java) class prints something unhelpful like Point@6d06d69c by default. A data class prints Point(x=1, y=2) — every property, readable, with zero code written.
equals() — Structural, Not Referential
A plain class's == compares references (are these the same object in memory?). A data class's == compares values — two separately-constructed Point(1, 2) instances are equal.
📄 copy()
Data classes also generate a copy() function, which creates a new instance with the same values, except for whichever properties you explicitly override:
This pairs naturally with everything from Chapter 1 — data classes are usually built from val properties, so copy() is the standard way to produce an "updated" version without ever mutating the original. It's the same immutable-update pattern as JavaScript's spread syntax ({ ...user, age: 31 }), but type-checked and built into the class itself.
🏢 Companion Objects
Kotlin has no static keyword. Instead, members that belong to the class itself (not an instance) live inside a companion object:
"Static" Members: Java vs Kotlin
| Java | Kotlin |
|---|---|
| static User guest() { return new User("Guest"); } | companion object { fun guest() = User("Guest") } |
| User.guest() | User.guest() |
The call syntax looks identical to Java's static call — User.guest() — but under the hood, a companion object is a real singleton object tied to the class, which is why it can also implement interfaces or hold its own properties, something a Java static block can't do.
💻 Coding Challenges
Challenge 1: A Class with an Init Block
Write a class BankAccount(val owner: String, var balance: Double) with an init block that uses require to reject a negative starting balance. Add a function deposit(amount: Double) that increases balance. Create an account, deposit twice, and print the final balance.
Goal: Combine constructor properties, init-block validation, and a regular method.
Challenge 2: Data Class Equality & toString
Declare a data class Book(val title: String, val author: String, val year: Int). Create two separate Book instances with identical values and print whether they're equal with ==. Print one of them directly and observe the auto-generated toString() output.
Goal: See structural equality and free toString() in action.
Challenge 3: copy() and a Companion Object
Using the same Book data class, use copy() to create a second-edition version of a book with an updated year, leaving the original unchanged. Then add a companion object with a function unknown() that returns a placeholder Book("Unknown", "Unknown", 0), and call it via Book.unknown().
Goal: Practice copy() for immutable updates and companion objects for factory-style functions.
Once Android chapters begin later on, data classes become the default shape for API response models, database entities, and UI state objects — anywhere a class exists mainly to hold values. The equals()/toString()/copy() trio this chapter covers isn't a niche feature; it's one of the most-used parts of the whole language.
🎯 What's Next
Next chapter: Inheritance & Interfaces — open/abstract classes, interfaces, polymorphism, and sealed classes.