Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
19: Data Classes
If you've spent any time in Java or C#, you know the pain of "POJOs" or "DTOs." You'd create a simple class to hold some data, and then you'd have to spend twenty minutes generating getters, setters, equals(), hashCode(), and toString() just so the class behaved predictably. In Kotlin, we have data classes, and they effectively kill that boilerplate entirely.
Why not just use a regular class for this?
You can, but you'll be doing a lot of manual labor. A regular class in Kotlin focuses on behavior. A data class focuses on data. When you mark a class with the data keyword, Kotlin automatically generates the plumbing for you based on the properties defined in the primary constructor.
// Instead of 50 lines of boilerplate, you get this:
data class UserProfile(
val id: UUID,
val username: String,
val email: String,
val bio: String
)
I always recommend using val (read-only) for data class properties. Data classes are at their best when they are immutable. If you need to change something, you don't mutate the object; you create a new version of it. I'll show you how to do that in a second.
What is Kotlin actually doing "under the hood"?
The magic is in the generated methods. The most immediate difference you'll notice is the toString() implementation. A regular class prints a memory address (like UserProfile@4f32b1), which is useless for debugging. A data class prints the actual content.
val user1 = UserProfile(UUID.randomUUID(), "kotlin_fan", "fan@example.com", "I love concise code")
val user2 = UserProfile(user1.id, "kotlin_fan", "fan@example.com", "I love concise code")
println(user1)
// Output: UserProfile(id=..., username=kotlin_fan, email=fan@example.com, bio=I love concise code)
println(user1 == user2)
// Output: true
Notice that user1 == user2 is true. In a regular class, this would be false because they are different objects in memory. But for a data class, Kotlin generates an equals() method that checks if the values inside the properties are the same. This is a lifesaver when you're comparing API responses or filtering lists.
How do I "update" a value if everything is a val?
Since we're leaning into immutability, you can't just do user.email = "new@email.com". Instead, Kotlin gives you the copy() method. This allows you to create a new instance of the class, changing only the specific properties you care about while keeping the rest exactly the same.
val updatedUser = user1.copy(email = "updated_fan@example.com")
println(updatedUser.username) // Still "kotlin_fan"
println(updatedUser.email) // Now "updated_fan@example.com"
This pattern is incredibly powerful in large-scale apps. It prevents those nasty bugs where one part of your code accidentally changes an object that another part of your code is still using.
Are there any catches or restrictions?
A few. First, a data class must have at least one parameter in its primary constructor, and all those parameters must be marked as val or var. You can't have a data class with an empty constructor.
Also, data classes cannot be abstract, open, sealed, or inner. They are designed to be final, simple containers. If you find yourself needing complex inheritance hierarchies, a data class probably isn't the right tool for that specific part of your architecture.
📋 Practical Task
Building a Shopping Cart Item Manager
You are building a small e-commerce module. You need to handle products that can be added to a cart, and occasionally, the price of a product changes while it's in the cart, or the quantity is updated.
Your Task:
- Create a data class named
CartItemwith the following properties:productId(Int),name(String),price(Double), andquantity(Int). - In your
mainfunction, create an instance of aCartItem(e.g., a "Mechanical Keyboard" at $120.00 with a quantity of 1). - The user decides to buy 3 of these keyboards instead of 1. Use the
copy()method to create a newCartItemwith the updated quantity. - The store applies a 10% discount to the price. Use the
copy()method again to create another version of the item with the discounted price. - Print all three versions of the item to the console to verify that the
toString()output correctly shows the changes at each step.
There are no comments for now.