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

equals() و hashCode()

جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 42 من 87.

عند مقارنة الكائنات في Java، يتحقق عامل التشغيل == مما إذا كان مرجعان يشيران إلى نفس موقع الذاكرة. لمقارنة المحتوى الفعلي للكائنات، تحتاج إلى تجاوز التابع equals().

public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
}

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

كلما قمت بتجاوز equals()، يجب عليك أيضاً تجاوز hashCode(). هذا عقد في Java: الكائنات المتساوية يجب أن تمتلك نفس رمز التجزئة (hash code). تعتمد المجموعات القائمة على التجزئة مثل HashMap و HashSet على هذه القاعدة لتعمل بشكل صحيح.

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

تقوم الطريقة المساعدة Objects.hash() بتوليد رمز تجزئة (hash code) من نفس الحقول المستخدمة في equals(). إذا كان لكائنين من نوع Person نفس الـ name والـ age، فسيتم اعتبارهما متساويين وسينتجان رموز تجزئة متطابقة، مما يضمن سلوكاً متسقاً في المجموعات (collections).

challenge icon

التحدي

سهل

لنقم ببناء نظام سجلات طلاب يقوم بتنفيذ مساواة الكائنات (object equality) بشكل صحيح. ستقوم بإنشاء فئة Student حيث يعتبر الطالبان متساويين إذا كان لهما نفس معرف الطالب والاسم — بغض النظر عما إذا كانا نفس الكائن في الذاكرة أم لا.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • Student.java: قم بإنشاء فئة تمثل طالباً بثلاثة حقول خاصة (private): studentId (String)، و name (String)، و gpa (double). قم بتضمين منشئ (constructor) لتهيئة جميع الحقول وطرق جلب (getter methods) لكل حقل.

    قم بإعادة تعريف (Override) الطريقة equals() بحيث يعتبر كائنان من نوع Student متساويين عندما يكون لهما نفس studentId وَ نفس name. اتبع النمط القياسي: تحقق من نفس المرجع، تحقق من القيمة null والنوع، ثم قارن الحقول ذات الصلة.

    قم بإعادة تعريف الطريقة hashCode() باستخدام Objects.hash() مع نفس الحقول المستخدمة في equals(). تذكر استيراد java.util.Objects في أعلى الملف الخاص بك.

  • Main.java: قم بتفعيل فئة Student الخاصة بك من خلال مقارنة كائنات الطلاب. ستتلقى ستة مدخلات تمثل طالبين: معرف الطالب الأول، اسمه، ومعدله التراكمي (GPA)، يليه معرف الطالب الثاني، اسمه، ومعدله التراكمي.

    قم بإنشاء كلا كائني Student، ثم قم بإجراء ثلاث مقارنات واطبع النتائج:

    1. اطبع Same reference: متبوعاً بما إذا كان الطالب الأول يساوي نفسه (يجب أن يكون دائماً true)
    2. اطبع Content equality: متبوعاً بما إذا كان الطالب الأول يساوي الطالب الثاني (بناءً على تنفيذك لـ equals)
    3. اطبع Hash codes match: متبوعاً بما إذا كان لكلا الطالبين نفس رمز التجزئة (hash code)

ستتلقى ستة مدخلات: معرف الطالب الأول (String)، اسم الطالب الأول (String)، المعدل التراكمي للطالب الأول (double)، معرف الطالب الثاني (String)، اسم الطالب الثاني (String)، والمعدل التراكمي للطالب الثاني (double).

لاحظ كيف أنه عندما يتطابق معرفا الطالبين واسماهما، فإن كلاً من equals() تعيد true وَ تتطابق رموز التجزئة الخاصة بهما — هذا هو العقد (contract) الذي يجب عليك الحفاظ عليه!

ورقة مرجعية

يتحقق عامل التشغيل == مما إذا كان مرجعان يشيران إلى نفس موقع الذاكرة، وليس ما إذا كانت الكائنات لها نفس المحتوى.

لمقارنة محتوى الكائنات، قم بتجاوز طريقة equals() باتباع هذا النمط:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;  // نفس المرجع
    if (obj == null || getClass() != obj.getClass()) return false;  // قيمة فارغة أو نوع مختلف
    Person person = (Person) obj;  // التحويل إلى النوع الصحيح
    return age == person.age && name.equals(person.name);  // مقارنة الحقول
}

كلما قمت بتجاوز equals()، يجب عليك أيضًا تجاوز hashCode(). هذا عقد في Java: يجب أن يكون للكائنات المتساوية نفس رمز التجزئة (hash code).

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

استخدم Objects.hash() مع نفس الحقول المستخدمة في equals(). يضمن ذلك عمل المجموعات القائمة على التجزئة مثل HashMap و HashSet بشكل صحيح.

جرّب بنفسك

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // قراءة بيانات الطالب الأول
        String id1 = scanner.nextLine();
        String name1 = scanner.nextLine();
        double gpa1 = scanner.nextDouble();
        scanner.nextLine(); // استهلاك السطر الجديد
        
        // قراءة بيانات الطالب الثاني
        String id2 = scanner.nextLine();
        String name2 = scanner.nextLine();
        double gpa2 = scanner.nextDouble();
        
        // TODO: إنشاء كائنين من النوع Student باستخدام بيانات الإدخال
        
        // TODO: طباعة "Same reference: " متبوعة بما إذا كان student1 يساوي نفسه
        
        // TODO: طباعة "Content equality: " متبوعة بما إذا كان student1 يساوي student2
        
        // TODO: طباعة "Hash codes match: " متبوعة بما إذا كان لكلا الطالبين نفس الـ hash code
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming