Skip to content

Kotlin: Class vs DataClass

Devrath edited this page Feb 3, 2024 · 11 revisions

Choosing b/w Data & Normal Classes

  • Use Data Classes: When you have a class primarily meant to hold data, and you want to benefit from the automatically generated methods. Data classes are particularly useful for model classes, DTOs (Data Transfer Objects), and similar scenarios.

  • Use Normal Classes: When you need more control over the behavior of your class or when you require mutable properties.

In Android development, both data classes and normal classes have their places, and the choice depends on the specific requirements of your application.

Key differences b/w normal & data class

Default Methods

Data classes:

They automatically generate some commonly used methods like toString(), equals(), hashCode(), and copy() for you, whereas in a normal class, you would need to manually implement these methods.

Normal classes:

You would need to implement these methods manually.

Comparing two instances

Data classes:

Comparing two instances based on the data it holds is simple

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

   val person1 = Person("John", 25)
   val person2 = Person("John", 25)

   println(person1 == person2) // true (equals() is automatically generated)

Normal classes:

Here we need to implement our own custom comparator or using equals to achieve the same

Using equals

class PersonNormalClass(var name: String, var age: Int) {
    // Override the equals method for custom comparison
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || javaClass != other.javaClass) return false

        // Check for equality based on name and age
        val otherPerson = other as PersonNormalClass
        return name == otherPerson.name && age == otherPerson.age
    }

    // Override the hashCode method to be consistent with the equals method
    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + age
        return result
    }
}

fun main() {
    // Create two instances of PersonNormalClass
    val person1 = PersonNormalClass("John", 25)
    val person2 = PersonNormalClass("John", 25)

    // Compare instances using equals method
    val areEqual = person1 == person2
    println("Are the instances equal: $areEqual")
}

Using comparator

import java.util.Comparator

class PersonComparator : Comparator<PersonNormalClass> {
    override fun compare(person1: PersonNormalClass, person2: PersonNormalClass): Int {
        // Compare based on name and then age
        val nameComparison = person1.name.compareTo(person2.name)
        if (nameComparison != 0) {
            return nameComparison
        }
        return Integer.compare(person1.age, person2.age)
    }
}

fun main() {
    // Create two instances of PersonNormalClass
    val person1 = PersonNormalClass("John", 25)
    val person2 = PersonNormalClass("John", 25)

    // Use the Comparator to compare instances
    val comparator = PersonComparator()
    val comparisonResult = comparator.compare(person1, person2)

    // Print the result of the comparison
    println("Comparison result: $comparisonResult")
}

Component Functions

Data classes:

Automatically provide component functions for properties, which can be useful in certain scenarios, like destructuring declarations.

   val (name, age) = person1 // Destructuring declaration using component functions

Normal classes:

There is no such feature using normal class

Copy Method

Data classes:

Provide a copy method, which allows you to create a copy of an instance with some properties changed.

   val modifiedPerson = person1.copy(age = 30)

Normal classes:

Such a feature is not there

No-arg Constructor

Data classes:

Data classes automatically generate a no-arg constructor if all the properties have default values.

   data class Student(val name: String = "", val age: Int = 0)

Normal classes:

This is not possible and you need to manually create it

In Data class - Shallow Copy vs Deep Copy

Shallow Copy

  • It creates a new object, But it does not create new objects that are contained within the object.
  • When there are nested level objects, The newly created object will reference the old references for the nested levels

Deep Copy

  • The newly created object not only is a new reference but the nested levels of the objects will also be the new references.

What kotlin's copy() provides, Is it shallow or deep copy

  • Kotlin's copy() provides the shallow copy()
  • If you need deep copy(), You need to implement it manually

Example of shallow copy() in kotlin

In this example, modifying person1.address.street would also affect person2.address.street because they refer to the same Address object. If you need a deep copy, you'd have to manually create a new Address object during the copy process.

data class Address(val street: String, val city: String)

data class Person(val name: String, val age: Int, val address: Address)

fun main() {
    val address = Address("Main St", "Cityville")
    val person1 = Person("Alice", 25, address)
    val person2 = person1.copy()

    // Both person1 and person2 will share the same Address object
    println(person1)
    println(person2)
}

Data class, Why is it thread-safe

  • A data class can be made immutable, which means that the fields of the class are closed for modification.
  • If we try to modify the fields, an exception will be thrown. Immutable data classes are thread-safe since their content cannot be changed.
  • When all properties of a data class are declared as val, the instances of that class become immutable, meaning their values cannot be changed after instantiation.
  • Since the data held by these classes cannot be modified once created, there is no risk of data corruption or unexpected behavior when accessed by multiple threads concurrently.
data class Person(val name: String, val age: Int)
  • In this case, an instance of the Person class, once created, cannot be modified. If you need to change a property's value, you have to create a new instance with the updated values. This immutability guarantees that the data remains consistent and predictable, making instances of the class thread-safe.
  • NOTE: However, it's important to note that while immutable data classes provide thread safety for the data they encapsulate, the overall thread safety of your application still depends on how you handle concurrency in other parts of your code, especially when dealing with shared mutable state.
Clone this wiki locally