Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

equals، hashCode، toString

جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 35 من 57.

ترث كل فئة toString() وequals() وhashCode() من Any. تعمل الإصدارات الموروثة على الهوية: تعطي toString() اسم الفئة ورمز تجزئة، وتكون equals() مساوية لـ true للكائن نفسه فقط. يؤدي تجاوز toString() إلى منح الكائنات نصًا مقروءًا، ويُستخدم ذلك بواسطة println وقوالب السلاسل النصية:

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

داخل main:

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

الناتج:

$12.50
total: $0.99

== يستدعي equals()، بينما يتحقق === مما إذا كان مرجعان يشيران إلى الكائن نفسه. للمقارنة حسب القيمة، أعد تعريف equals. وسيطها هو Any?، لذا تتحقق أولًا من النوع ثم تقارن الخصائص ذات الصلة:

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()
}

داخل main:

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

الناتج:

true
false

يجب أن تكون للكائنات المتساوية رموز تجزئة متساوية. تبحث HashSet وHashMap عن الإدخالات باستخدام hashCode() أولًا، ولا تستدعي equals() إلا بعد ذلك، لذا فإن تجاوز equals دون hashCode يجعل الكائنات المتساوية تبدو مختلفة فيهما. تجاوز كليهما دائمًا، واحسبهما من الخصائص نفسها:

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"
}

داخل 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)

الناتج:

2
true

يُنشئ صنف البيانات الثلاثة جميعها من خصائص مُنشئه. اكتبها بنفسك عندما ينبغي أن تستخدم المساواة عددًا أقل من الخصائص أو صيغةً موحّدة، مثل عنوان بريد إلكتروني يتجاهل حالة الأحرف:

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>"
}

داخل main:

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

الناتج:

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

التحدي

سهل

يكون المنتجان متماثلين عندما تتطابق قيمة sku الخاصة بهما، بغضّ النظر عمّا تقوله الحقول الأخرى. تجاوز equals() وhashCode() في Product بالاعتماد على sku فقط، وطبّق toString() بحيث تتم طباعة المنتج بالشكل A1 tea 450. ثم أكمل mergeCatalog(products)، بحيث يحتفظ بأول ظهور لكل منتج بالترتيب الأصلي.

يقرأ الكود الموفَّر المنتجات على هيئة أسطر بتنسيق sku,name,price، وسطر أخير يحتوي على sku للبحث عنه. ويطبع كل منتج تم الاحتفاظ به، وremoved: 2 وA1 in catalog: true، مع البحث عن sku في HashSet.

يجب أن تضع كودك في Product.kt وFunctions.kt. يحتوي Main.kt على كود الإدخال/الإخراج الموفَّر، ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    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 iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Kotlin