Menu
CoddyTech

equals, hashCode, toString

Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 35 trên 57.

Mọi lớp đều kế thừa toString(), equals() và hashCode() từ Any. Các phiên bản được kế thừa hoạt động dựa trên danh tính: toString() trả về tên lớp và mã băm, còn equals() chỉ là true khi đó là cùng một đối tượng. Ghi đè toString() giúp đối tượng có văn bản dễ đọc, được dùng bởi println và mẫu chuỗi:

class Money(val cents: Int) {
    override fun toString() = "$%d.%02d".format(cents / 100, cents % 100)
}

Bên trong main:

val price = Money(1250)
println(price)
println("total: ${Money(99)}")

Đầu ra:

$12.50
total: $0.99

== gọi equals(), còn === kiểm tra xem hai tham chiếu có trỏ đến cùng một đối tượng hay không. Để so sánh theo giá trị, hãy ghi đè equals. Tham số của nó là Any?, nên hàm kiểm tra kiểu trước rồi so sánh các thuộc tính liên quan:

class Isbn(code: String) {
    val digits = code.filter { it.isDigit() }

    override fun equals(other: Any?) = other is Isbn && other.digits == digits
    override fun hashCode() = digits.hashCode()
}

Bên trong main:

val a = Isbn("978-0-441-17271-9")
val b = Isbn("9780441172719")
println(a == b)
println(a === b)

Đầu ra:

true
false

Các đối tượng bằng nhau phải có mã băm bằng nhau. HashSet và HashMap tìm các mục bằng hashCode() trước, rồi mới gọi equals(), vì vậy nếu ghi đè equals mà không ghi đè hashCode, các đối tượng bằng nhau sẽ có vẻ khác nhau trong chúng. Luôn ghi đè cả hai, tính từ cùng các thuộc tính:

class Isbn(code: String) {
    val digits = code.filter { it.isDigit() }

    override fun equals(other: Any?) = other is Isbn && other.digits == digits
    override fun hashCode() = digits.hashCode()
    override fun toString() = "ISBN $digits"
}

Bên trong main:

val shelf = hashSetOf(Isbn("978-0-441-17271-9"), Isbn("9780441172719"), Isbn("0-14-143951-3"))
println(shelf.size)
println(Isbn("9780441172719") in shelf)

Đầu ra:

2
true

Một lớp dữ liệu tự tạo cả ba phương thức từ các thuộc tính của hàm khởi tạo. Hãy tự viết các phương thức này khi phép so sánh bằng nhau nên dùng ít thuộc tính hơn hoặc một dạng đã chuẩn hóa, chẳng hạn như địa chỉ email không phân biệt chữ hoa chữ thường:

class User(val email: String, val name: String) {
    private val key = email.trim().lowercase()

    override fun equals(other: Any?) = other is User && other.key == key
    override fun hashCode() = key.hashCode()
    override fun toString() = "$name <$key>"
}

Bên trong main:

val users = listOf(User("Ada@Example.com", "Ada"), User("ada@example.com ", "A. Lovelace"))
println(users.distinct())

Đầu ra:

[Ada <ada@example.com>]
challenge icon

Thử thách

Dễ

Hai sản phẩm giống nhau khi sku của chúng khớp nhau, bất kể các trường khác có giá trị gì. Chỉ ghi đè equals() và hashCode() trong Product dựa trên sku, đồng thời ghi đè toString() để sản phẩm được in ra dưới dạng A1 tea 450. Sau đó hoàn thành mergeCatalog(products), hàm này giữ lại lần xuất hiện đầu tiên của mỗi sản phẩm theo thứ tự ban đầu.

Code có sẵn đọc các sản phẩm từ các dòng theo định dạng sku,name,price và dòng cuối cùng chứa một sku cần tra cứu. Code in ra mọi sản phẩm được giữ lại, removed: 2 và A1 in catalog: true, đồng thời tra cứu sku trong một HashSet.

Code của bạn nằm trong Product.kt và Functions.kt. Main.kt chứa code đầu vào/đầu ra có sẵn và bạn không thể chỉnh sửa tệp này.

Tự làm thử

fun main() {
    // Đoạn code đầu vào/đầu ra đã cung cấp: giữ nguyên
    val input = generateSequence(::readLine).toList()
    val products = input.dropLast(1).map { line ->
        val (sku, name, price) = line.split(",")
        Product(sku, name, price.toInt())
    }
    val kept = mergeCatalog(products)
    for (p in kept) println(p)
    println("removed: ${products.size - kept.size}")
    val probe = Product(input.last(), "?", 0)
    println("${input.last()} in catalog: ${probe in kept.toHashSet()}")
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Kotlin