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).
التحدي
سهللنقم ببناء نظام سجلات طلاب يقوم بتنفيذ مساواة الكائنات (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، ثم قم بإجراء ثلاث مقارنات واطبع النتائج:
- اطبع
Same reference:متبوعاً بما إذا كان الطالب الأول يساوي نفسه (يجب أن يكون دائماً true) - اطبع
Content equality:متبوعاً بما إذا كان الطالب الأول يساوي الطالب الثاني (بناءً على تنفيذك لـ equals) - اطبع
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
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر