Kotlin Data Classes

Classes designed primarily to hold data with automatic functionality

📊 What are Data Classes?

Data classes in Kotlin are special classes designed to hold data. They automatically generate useful methods like equals(), hashCode(), toString(), and copy(), reducing boilerplate code while providing powerful functionality for data manipulation and comparison.


// Simple data class example
data class Person(val name: String, val age: Int)
                                    

Data Class Features

🔍

Auto-generated Methods

equals(), hashCode(), toString()

data class User(val name: String)
// Automatically gets comparison methods
📋

Copy Function

Create copies with modifications

val user2 = user1.copy(name = "New Name")
🔧

Destructuring

Extract properties easily

val (name, age) = person
📦

Primary Constructor

At least one parameter required

data class Point(val x: Int, val y: Int)

🔹 Basic Data Class

Here's how to create and use a simple data class:

data class Student(
    val name: String,
    val age: Int,
    val grade: String
)

fun main() {
    val student1 = Student("Alice", 20, "A")
    val student2 = Student("Alice", 20, "A")
    val student3 = Student("Bob", 21, "B")
    
    // Automatic toString()
    println(student1)
    
    // Automatic equals()
    println("student1 == student2: ${student1 == student2}")
    println("student1 == student3: ${student1 == student3}")
    
    // Automatic hashCode()
    println("student1 hashCode: ${student1.hashCode()}")
    println("student2 hashCode: ${student2.hashCode()}")
}

Output:

Student(name=Alice, age=20, grade=A)
student1 == student2: true
student1 == student3: false
student1 hashCode: -1706434656
student2 hashCode: -1706434656

🔹 Copy Function

Create modified copies of data class instances:

data class Product(
    val name: String,
    val price: Double,
    val category: String,
    val inStock: Boolean = true
)

fun main() {
    val laptop = Product("Gaming Laptop", 1299.99, "Electronics")
    
    println("Original: $laptop")
    
    // Copy with price change
    val discountedLaptop = laptop.copy(price = 999.99)
    println("Discounted: $discountedLaptop")
    
    // Copy with multiple changes
    val outOfStockLaptop = laptop.copy(
        price = 1199.99,
        inStock = false
    )
    println("Out of stock: $outOfStockLaptop")
    
    // Original remains unchanged
    println("Original unchanged: $laptop")
}

Output:

Original: Product(name=Gaming Laptop, price=1299.99, category=Electronics, inStock=true)
Discounted: Product(name=Gaming Laptop, price=999.99, category=Electronics, inStock=true)
Out of stock: Product(name=Gaming Laptop, price=1199.99, category=Electronics, inStock=false)
Original unchanged: Product(name=Gaming Laptop, price=1299.99, category=Electronics, inStock=true)

🔹 Destructuring Declarations

Extract properties from data classes easily:

data class Coordinate(val x: Int, val y: Int, val z: Int)

data class Person(val firstName: String, val lastName: String, val age: Int)

fun main() {
    val point = Coordinate(10, 20, 30)
    
    // Destructuring assignment
    val (x, y, z) = point
    println("Point coordinates: x=$x, y=$y, z=$z")
    
    // Partial destructuring
    val (xOnly, yOnly) = point
    println("Only x and y: x=$xOnly, y=$yOnly")
    
    // Using underscore to skip values
    val (_, _, zOnly) = point
    println("Only z: z=$zOnly")
    
    // Destructuring in function parameters
    fun printPersonInfo(person: Person) {
        val (first, last, age) = person
        println("Name: $first $last, Age: $age")
    }
    
    val person = Person("John", "Doe", 25)
    printPersonInfo(person)
}

Output:

Point coordinates: x=10, y=20, z=30
Only x and y: x=10, y=20
Only z: z=30
Name: John Doe, Age: 25

🔹 Data Classes in Collections

Data classes work great with collections due to proper equals() and hashCode():

data class Book(val title: String, val author: String, val year: Int)

fun main() {
    val books = listOf(
        Book("1984", "George Orwell", 1949),
        Book("To Kill a Mockingbird", "Harper Lee", 1960),
        Book("1984", "George Orwell", 1949), // Duplicate
        Book("The Great Gatsby", "F. Scott Fitzgerald", 1925)
    )
    
    println("All books:")
    books.forEach { println("  $it") }
    
    // Remove duplicates using Set
    val uniqueBooks = books.toSet()
    println("\nUnique books:")
    uniqueBooks.forEach { println("  $it") }
    
    // Find books by author
    val orwellBooks = books.filter { it.author == "George Orwell" }
    println("\nBooks by George Orwell:")
    orwellBooks.forEach { println("  $it") }
    
    // Group by decade
    val booksByDecade = books.groupBy { (it.year / 10) * 10 }
    println("\nBooks by decade:")
    booksByDecade.forEach { (decade, bookList) ->
        println("  ${decade}s: ${bookList.size} books")
    }
}

Output:

All books:
Book(title=1984, author=George Orwell, year=1949)
Book(title=To Kill a Mockingbird, author=Harper Lee, year=1960)
Book(title=1984, author=George Orwell, year=1949)
Book(title=The Great Gatsby, author=F. Scott Fitzgerald, year=1925)

Unique books:
Book(title=1984, author=George Orwell, year=1949)
Book(title=To Kill a Mockingbird, author=Harper Lee, year=1960)
Book(title=The Great Gatsby, author=F. Scott Fitzgerald, year=1925)

🔹 Data Class Requirements

Understanding data class constraints and best practices:

// ✅ Valid data class
data class ValidUser(val name: String, val email: String)

// ✅ Data class with default values
data class UserProfile(
    val username: String,
    val isActive: Boolean = true,
    val joinDate: String = "2024-01-01"
)

// ✅ Data class with additional properties
data class Employee(val id: Int, val name: String) {
    var department: String = "Unassigned" // Not part of equals/hashCode
    
    fun getDisplayName() = "Employee #$id: $name"
}

fun main() {
    val user1 = ValidUser("Alice", "[email protected]")
    val user2 = ValidUser("Alice", "[email protected]")
    
    println("Users equal: ${user1 == user2}")
    
    val profile = UserProfile("john_doe")
    println("Profile: $profile")
    
    val emp1 = Employee(1, "John")
    val emp2 = Employee(1, "John")
    emp1.department = "IT"
    emp2.department = "HR"
    
    // Still equal because department is not in primary constructor
    println("Employees equal: ${emp1 == emp2}")
    println("Employee 1: ${emp1.getDisplayName()}, Dept: ${emp1.department}")
    println("Employee 2: ${emp2.getDisplayName()}, Dept: ${emp2.department}")
}

Output:

Users equal: true
Profile: UserProfile(username=john_doe, isActive=true, joinDate=2024-01-01)
Employees equal: true
Employee 1: Employee #1: John, Dept: IT
Employee 2: Employee #1: John, Dept: HR

🧠 Test Your Knowledge

Which methods are automatically generated for data classes?