equals() و hashCode()
جزء من قسم البرمجة كائنية التوجه في رحلة 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: يجب أن يكون للكائنات المتساوية رمز التجزئة نفسه. تعتمد المجموعات القائمة على التجزئة، مثل HashMap وHashSet، على هذه القاعدة لتعمل بشكل صحيح.
@Override
public int hashCode() {
return Objects.hash(name, age);
}تُنشئ الطريقة المساعدة Objects.hash() رمز تجزئة من الحقول نفسها المستخدمة في equals(). إذا كان لكائني Person الاسم والعمر نفسيهما، فسيُعتبران متساويين وسيُنتجان رمزي تجزئة متطابقين، مما يضمن سلوكًا متسقًا في المجموعات.
التحدي
سهللنبنِ نظامًا لسجلات الطلاب يطبّق مساواة الكائنات بشكل صحيح. ستنشئ فئة Student بحيث يُعَدّ طالبان متساويين إذا كان لهما رقم الطالب والاسم نفسيهما، بصرف النظر عمّا إذا كانا الكائن نفسه في الذاكرة.
ستنظّم الشيفرة في ملفين:
Student.java: أنشئ فئة تمثّل طالبًا بثلاثة حقول خاصة:studentId(String)، وname(String)، وgpa(double). أضف مُنشئًا لتهيئة جميع الحقول، وطرائق getter لكل حقل.أعِد تعريف الطريقة
equals()بحيث يُعَدّ كائنان من فئة Student متساويين عندما يكون لهماstudentIdنفسه وnameنفسه. اتّبع النمط المعتاد: تحقّق من تطابق المرجع، ثم تحقّق من null والنوع، وبعد ذلك قارن الحقول ذات الصلة.أعِد تعريف الطريقة
hashCode()باستخدامObjects.hash()مع الحقول نفسها المستخدمة فيequals(). تذكّر استيرادjava.util.Objectsفي أعلى الملف.Main.java: اجعل فئة Student تعمل من خلال مقارنة كائنات الطلاب. ستتلقى ستة مدخلات تمثّل طالبين: رقم الطالب الأول واسمه ومعدّله التراكمي، ثم رقم الطالب الثاني واسمه ومعدّله التراكمي.أنشئ كائني Student، ثم أجرِ ثلاث مقارنات واطبع النتائج:
- اطبع
Same reference:متبوعًا بما إذا كان الطالب الأول مساويًا لنفسه (يجب أن تكون النتيجة دائمًا true) - اطبع
Content equality:متبوعًا بما إذا كان الطالب الأول مساويًا للطالب الثاني (استنادًا إلى تطبيقك للطريقة equals) - اطبع
Hash codes match:متبوعًا بما إذا كان لكلا الطالبين رمز hash نفسه
- اطبع
ستتلقى ستة مدخلات: رقم الطالب الأول (String)، واسم الطالب الأول (String)، والمعدّل التراكمي للطالب الأول (double)، ورقم الطالب الثاني (String)، واسم الطالب الثاني (String)، والمعدّل التراكمي للطالب الثاني (double).
لاحظ أنه عندما يكون لدى طالبين رقما طالب واسمان متطابقان، تُرجع كل من equals() القيمة true وتتطابق رموز hash الخاصة بهما. هذا هو العقد الذي يجب عليك الحفاظ عليه!
جرّب بنفسك
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: " متبوعًا بما إذا كان الطالبان يمتلكان رمز التجزئة نفسه
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت