Skip to main content

Kotlin Equality

Understanding how equality works is a fundamental concept in programming. Kotlin offers multiple ways to check if two objects are equal, with clear distinctions between comparing references and comparing content. This article explores Kotlin's approach to equality and how to use it effectively in your code.

Introduction to Equality in Kotlin

In Kotlin, there are two main types of equality:

  1. Structural equality (value equality) - checks if two objects have the same content
  2. Referential equality - checks if two references point to the same object in memory

Let's explore how Kotlin implements these concepts and how you can use them in your code.

Structural Equality (==)

In Kotlin, the == operator (double equals) checks for structural equality. This is different from many other languages like Java, where == checks for referential equality.

When you use == in Kotlin, it automatically calls the equals() method to compare the contents of objects.

kotlin
val str1 = "Hello"
val str2 = "Hello"
val str3 = "World"

println(str1 == str2) // true - same content
println(str1 == str3) // false - different content

Output:

true
false

Under the hood, a == b is translated to a?.equals(b) ?: (b === null), which means it handles null values safely.

Null Safety with Equality

Kotlin's equality operators are null-safe by design:

kotlin
val a: String? = null
val b: String? = null
val c: String? = "Hello"

println(a == b) // true - both are null
println(a == c) // false - null is not equal to a non-null value

Output:

true
false

Referential Equality (===)

If you need to check whether two references point to exactly the same object (identity check), use the triple equals operator ===.

kotlin
val list1 = mutableListOf(1, 2, 3)
val list2 = mutableListOf(1, 2, 3)
val list3 = list1

println(list1 == list2) // true - same content
println(list1 === list2) // false - different objects
println(list1 === list3) // true - same object reference

Output:

true
false
true

Implementing Custom Equality

Kotlin classes inherit the equals() method from the Any class. By default, this implementation checks for referential equality. To implement custom equality behavior, you need to override the equals() method.

Proper equals() Implementation

When implementing equals(), make sure to:

  1. Check if the object is the same reference (===)
  2. Check if the object is of the correct type
  3. Compare the relevant properties
kotlin
data class Person(val name: String, val age: Int)

// For data classes, equals() is automatically generated

class Student(val id: Int, val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Student) return false

return id == other.id && name == other.name
}

// Always override hashCode when overriding equals
override fun hashCode(): Int {
var result = id
result = 31 * result + name.hashCode()
return result
}
}

Let's test our custom equality:

kotlin
val student1 = Student(1, "Alice")
val student2 = Student(1, "Alice")
val student3 = Student(2, "Bob")

println(student1 == student2) // true - same content
println(student1 == student3) // false - different content

Output:

true
false

Data Classes and Equality

Kotlin's data classes automatically implement equals(), hashCode(), and other utility methods based on the properties declared in the primary constructor.

kotlin
data class Product(val id: String, val name: String, val price: Double)

val product1 = Product("001", "Laptop", 999.99)
val product2 = Product("001", "Laptop", 999.99)
val product3 = Product("002", "Phone", 599.99)

println(product1 == product2) // true - same content
println(product1 == product3) // false - different content

Output:

true
false

Equality in Collections

Kotlin's collection methods like contains, indexOf, and remove use structural equality (==) to find elements:

kotlin
data class User(val id: Int, val name: String)

val users = listOf(
User(1, "Alice"),
User(2, "Bob"),
User(3, "Charlie")
)

val searchUser = User(2, "Bob")

println(users.contains(searchUser)) // true - found by content equality
println(users.indexOf(searchUser)) // 1 - found at index 1

Output:

true
1

Real-World Example: Entity Comparison in Applications

Consider a scenario where you're building a messaging application. You might have a Message class representing chat messages:

kotlin
data class Message(
val id: String,
val sender: String,
val content: String,
val timestamp: Long
)

// In a messaging app, you might compare messages by ID only
class MessageView(val id: String, val content: String, val isRead: Boolean) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MessageView) return false

// We consider messages equal if they have the same ID,
// regardless of read status
return id == other.id
}

override fun hashCode(): Int {
return id.hashCode()
}
}

// Usage
fun main() {
val message1 = MessageView("msg-123", "Hello", false)
val message2 = MessageView("msg-123", "Hello", true)
val message3 = MessageView("msg-456", "Hello", false)

println(message1 == message2) // true - same message ID
println(message1 == message3) // false - different message ID

// This can be useful for finding and updating messages in a list
val messageList = mutableListOf(message1, message3)
val indexToUpdate = messageList.indexOf(message2) // Will find message1 because IDs match

println("Index to update: $indexToUpdate") // 0
}

Output:

true
false
Index to update: 0

Best Practices for Equality in Kotlin

  1. Use == by default for checking equality in most cases
  2. Use === only when you need to check if two references point to the same object
  3. Always override hashCode() when overriding equals()
  4. Consider using data classes when you need automatic equality implementations
  5. Be clear about what properties contribute to equality in your class design

Common Pitfalls

Forgetting to Override hashCode()

If you override equals() but forget to override hashCode(), your objects will behave inconsistently in hash-based collections like HashMap and HashSet.

kotlin
class BadExample(val id: Int) {
override fun equals(other: Any?): Boolean {
if (other !is BadExample) return false
return id == other.id
}

// Missing hashCode() implementation!
}

// This can cause unexpected behavior in hash-based collections
val bad1 = BadExample(1)
val bad2 = BadExample(1)

val map = hashMapOf(bad1 to "Value")
println(map[bad2]) // Might be null, despite bad1 and bad2 being "equal"

Inconsistent Equality Implementation

Make sure your equals() implementation is consistent with your object's behavior:

kotlin
class Counter(var count: Int) {
// Bad practice: equals depends on mutable state
override fun equals(other: Any?): Boolean {
if (other !is Counter) return false
return count == other.count
}

override fun hashCode(): Int {
return count
}
}

val counters = hashSetOf(Counter(5))
val myCounter = Counter(5)
println(myCounter in counters) // true

myCounter.count = 6
println(myCounter in counters) // false, but the hash set is now potentially corrupted

Summary

Understanding equality in Kotlin is essential for writing correct and predictable code:

  • Use == for structural equality (content comparison)
  • Use === for referential equality (identity comparison)
  • Kotlin's == operator is null-safe
  • Override equals() and hashCode() together when implementing custom equality behavior
  • Data classes provide automatic implementations of equality based on constructor properties

By mastering these concepts, you'll avoid common bugs related to equality and write more robust Kotlin applications.

Exercises

  1. Create a User class with id, name, and email properties. Implement equality so that users are considered equal if they have the same ID, regardless of other properties.

  2. Create a program that demonstrates how changing an object's properties after adding it to a HashSet can lead to unexpected behavior.

  3. Write a function that can find duplicate elements in a list according to custom equality criteria.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)