Equality (So sánh bằng) trong Kotlin
🎯 Mục tiêu: Hiểu rõ hai loại so sánh bằng trong Kotlin: Structural equality (
==) và Referential equality (===), cùng các trường hợp đặc biệt với số thực và mảng.
💡 Tổng quan
Kotlin cung cấp hai loại so sánh bằng:
- Structural equality (
==): So sánh nội dung/giá trị (dùng hàmequals()). - Referential equality (
===): So sánh địa chỉ vùng nhớ (hai tham chiếu cùng trỏ về một object).
📝 Structural Equality (==)
Structural equality kiểm tra xem hai đối tượng có “tương đương” về mặt nội dung hay không.
Toán tử == được dịch sang:
a == b // Tương đương với: a?.equals(b) ?: (b === null)Điều này có nghĩa là == an toàn với null. Nếu a là null, nó sẽ kiểm tra xem b có phải là null không (dùng ===).
fun main() {
val a = "hello"
val b = "hello"
val c = null
val d = null
println(a == b) // true (nội dung giống nhau)
println(a == c) // false
println(c == d) // true (đều là null)
}Với Custom Class
Mặc định, các class trong Kotlin kế thừa equals() từ Any, so sánh theo tham chiếu (giống ===).
- Data classes: Tự động override
equals()để so sánh các properties trong primary constructor. - Regular classes: Cần tự override
equals()nếu muốn so sánh theo nội dung.
class Point(val x: Int, val y: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Point) return false
// So sánh nội dung
return this.x == other.x && this.y == other.y
}
// Luôn override hashCode khi override equals!
override fun hashCode(): Int = 31 * x + y
}🔗 Referential Equality (===)
Referential equality kiểm tra xem hai biến có trỏ đến cùng một đối tượng trong bộ nhớ hay không.
fun main() {
val a = Integer(100)
val b = Integer(100)
val c = a
println(a == b) // true (giá trị giống nhau)
println(a === b) // false (hai object khác nhau)
println(a === c) // true (cùng trỏ đến object a)
}Với các kiểu nguyên thủy (như Int) tại runtime, === tương đương với ==.
🔢 So sánh số thực (Floating-point)
Khi các toán hạng là Float hoặc Double (tĩnh), phép so sánh tuân theo chuẩn IEEE 754.
Tuy nhiên, khi so sánh trong ngữ cảnh generic hoặc Any (được boxing), logic sẽ thay đổi để dảm bảo tính nhất quán của equals() và Comparable:
NaNbằng chính nó.NaNlớn hơnPOSITIVE_INFINITY.-0.0không bằng0.0.
val zero = 0.0
val negZero = -0.0
println(zero == negZero) // true (IEEE 754)
val boxedZero: Any = 0.0
val boxedNegZero: Any = -0.0
println(boxedZero == boxedNegZero) // false (Structural equality cho object)📦 So sánh Mảng (Array Equality)
Mảng trong Kotlin không override equals() để so sánh phần tử (giống Java). arr1 == arr2 chỉ so sánh tham chiếu.
Để so sánh nội dung mảng, dùng contentEquals() hoặc contentDeepEquals():
val arr1 = arrayOf(1, 2, 3)
val arr2 = arrayOf(1, 2, 3)
println(arr1 == arr2) // false (khác tham chiếu)
println(arr1.contentEquals(arr2)) // true (cùng nội dung)🛠️ Thực hành
Bài tập 1: Data Class vs Regular Class
data class DataUser(val name: String)
class RegularUser(val name: String)
fun main() {
val d1 = DataUser("Alice")
val d2 = DataUser("Alice")
val r1 = RegularUser("Bob")
val r2 = RegularUser("Bob")
// TODO: So sánh d1 với d2, r1 với r2 bằng `==` và giải thích kết quả
}Lời giải:
println(d1 == d2) // true (Data class tự override equals)
println(r1 == r2) // false (Regular class dùng equals mặc định là ===)✅ Checklist
- Hiểu sự khác biệt giữa
==và=== - Biết cách override
equals()cho class thường - Hiểu hành vi so sánh
NaNvà-0.0khi boxing - Dùng
contentEqualsđể so sánh mảng