أهلاً! سنتحدث اليوم عن طريقتين مهمتين في Java: equals() و hashCode() . هذه ليست المرة الأولى التي نلتقي بهم: تبدأ دورة CodeGym بدرس قصير
حول يساوي() — اقرأه إذا نسيته أو لم تراه من قبل... في درس اليوم، نحن' سأتحدث عن هذه المفاهيم بالتفصيل. وصدقوني، لدينا شيء لنتحدث عنه! ولكن قبل أن ننتقل إلى الجديد، دعونا نقوم بتحديث ما قمنا بتغطيته بالفعل :) كما تتذكر، عادة ما تكون فكرة مقارنة كائنين باستخدام عامل التشغيل == فكرة سيئة، لأن == يقارن بين المراجع. هذا هو مثالنا مع السيارات من درس حديث:
public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ferrari";
car1.maxSpeed = 300;
Car car2 = new Car();
car2.model = "Ferrari";
car2.maxSpeed = 300;
System.out.println(car1 == car2);
}
}
إخراج وحدة التحكم:
false
يبدو أننا أنشأنا كائنين متطابقين في السيارة : قيم الحقول المقابلة لكائني السيارة هي نفسها، لكن نتيجة المقارنة لا تزال خاطئة. نحن نعرف السبب بالفعل: تشير مراجع car1 و car2 إلى عناوين ذاكرة مختلفة، لذا فهما غير متساويين. لكننا ما زلنا نريد المقارنة بين الشيئين، وليس مرجعين. أفضل حل لمقارنة الكائنات هو طريقة يساوي () .
يساوي () الأسلوب
ربما تتذكر أننا لا ننشئ هذه الطريقة من الصفر، بل نتجاوزها: يتم تعريف طريقة يساوي () في فئة الكائن . ومع ذلك، في شكله المعتاد، فهو قليل الفائدة:public boolean equals(Object obj) {
return (this == obj);
}
هذه هي الطريقة التي يتم بها تعريف طريقة يساوي () في فئة الكائن . هذه مقارنة بين المراجع مرة أخرى. لماذا جعلوها هكذا؟ حسنًا، كيف يعرف منشئو اللغة أي الكائنات في برنامجك تعتبر متساوية وأيها ليست كذلك؟ :) هذه هي النقطة الرئيسية في طريقة يساوي () — منشئ الفئة هو الذي يحدد الخصائص المستخدمة عند التحقق من مساواة كائنات الفئة. ثم تقوم بتجاوز طريقة يساوي () في صفك. إذا كنت لا تفهم تمامًا معنى "يحدد الخصائص"، فلنأخذ مثالاً. إليك فئة بسيطة تمثل الرجل: Man
.
public class Man {
private String noseSize;
private String eyesColor;
private String haircut;
private boolean scars;
private int dnaCode;
public Man(String noseSize, String eyesColor, String haircut, boolean scars, int dnaCode) {
this.noseSize = noseSize;
this.eyesColor = eyesColor;
this.haircut = haircut;
this.scars = scars;
this.dnaCode = dnaCode;
}
// Getters, setters, etc.
}
لنفترض أننا نكتب برنامجًا يحتاج إلى تحديد ما إذا كان شخصان توأمًا متطابقًا أم مجرد متشابهين. لدينا خمس خصائص: حجم الأنف، ولون العين، ونمط الشعر، ووجود ندوب، ونتائج اختبار الحمض النووي (للتبسيط، نمثل ذلك كرمز صحيح). أي من هذه الخصائص تعتقد أنها ستسمح لبرنامجنا بالتعرف على التوائم المتماثلة؟ وبطبيعة الحال، فإن اختبار الحمض النووي فقط يمكن أن يوفر الضمان. يمكن أن يكون لدى شخصين نفس لون العين، وقصة الشعر، والأنف، وحتى الندوب - هناك الكثير من الناس في العالم، ومن المستحيل ضمان عدم وجود أي شبيهين هناك. لكننا بحاجة إلى آلية موثوقة: فقط نتيجة اختبار الحمض النووي هي التي ستسمح لنا بالتوصل إلى نتيجة دقيقة. ماذا يعني هذا بالنسبة لطريقتنا equals()
؟ نحن بحاجة إلى تجاوزه في Man
الفصل، مع مراعاة متطلبات برنامجنا. يجب أن تقارن الطريقة int dnaCode
مجال الكائنين. إذا كانت متساوية، فإن الكائنات متساوية.
@Override
public boolean equals(Object o) {
Man man = (Man) o;
return dnaCode == man.dnaCode;
}
هل هو حقا بهذه البساطة؟ ليس حقيقيًا. لقد أغفلنا شيئًا ما. بالنسبة لأشياءنا، حددنا حقلًا واحدًا فقط ذا صلة بتحقيق المساواة بين الكائنات: dnaCode
. تخيل الآن أنه ليس لدينا حقل واحد، بل 50 حقلاً ذا صلة. وإذا كانت جميع الحقول الخمسين لكائنين متساوية، فإن الكائنين متساويان. مثل هذا السيناريو ممكن أيضا. المشكلة الرئيسية هي أن تحقيق المساواة من خلال مقارنة خمسين مجالاً هي عملية تستغرق وقتاً طويلاً وتستهلك موارد كثيرة. الآن تخيل أنه بالإضافة إلى Man
فصلنا، لدينا Woman
فصل يحتوي على نفس الحقول الموجودة في Man
. إذا استخدم مبرمج آخر دروسنا، فيمكنه بسهولة كتابة تعليمات برمجية مثل هذا:
public static void main(String[] args) {
Man man = new Man(........); // A bunch of parameters in the constructor
Woman woman = new Woman(.........); // The same bunch of parameters.
System.out.println(man.equals(woman));
}
في هذه الحالة، التحقق من قيم الحقل لا معنى له: يمكننا أن نرى بسهولة أن لدينا كائنات من فئتين مختلفتين، لذلك لا توجد طريقة يمكن أن تكون متساوية! هذا يعني أنه يجب علينا إضافة فحص إلى equals()
الطريقة، ومقارنة فئات الكائنات المقارنة. من الجيد أننا فكرنا في ذلك!
@Override
public boolean equals(Object o) {
if (getClass() != o.getClass()) return false;
Man man = (Man) o;
return dnaCode == man.dnaCode;
}
لكن ربما نسينا شيئًا آخر؟ حسنًا... على الأقل، يجب علينا التحقق من أننا لا نقارن كائنًا بنفسه! إذا كان المرجعان A وB يشيران إلى نفس عنوان الذاكرة، فهما نفس الكائن، ولا نحتاج إلى إضاعة الوقت ومقارنة 50 حقلاً.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (getClass() != o.getClass()) return false;
Man man = (Man) o;
return dnaCode == man.dnaCode;
}
كما أنه لا يضر إضافة علامة اختيار إلى null
: لا يمكن أن يكون أي كائن مساويًا لـ null
. لذا، إذا كانت معلمة الطريقة فارغة، فلا فائدة من إجراء فحوصات إضافية. مع أخذ كل هذا في الاعتبار، تبدو equals()
طريقتنا للفصل Man
كما يلي:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Man man = (Man) o;
return dnaCode == man.dnaCode;
}
نقوم بإجراء كافة الفحوصات الأولية المذكورة أعلاه. في نهاية اليوم إذا:
- نحن نقارن كائنين من نفس الفئة
- والكائنات المقارنة ليست نفس الكائن
- والكائن الذي تم تمريره ليس كذلك
null
dnaCode
مجالات الجسمين. عند تجاوز equals()
الطريقة، تأكد من مراعاة هذه المتطلبات:
-
الانعكاسية.
عند
equals()
استخدام الطريقة لمقارنة أي كائن بنفسه، يجب أن يُرجع صحيحًا.
لقد امتثلنا بالفعل لهذا المطلب. طريقتنا تشمل:if (this == o) return true;
-
تناظر.
إذا
a.equals(b) == true
، فلاb.equals(a)
بد من العودةtrue
.
طريقتنا تلبي هذا المطلب أيضًا. -
عبورية.
إذا كان هناك كائنان يساويان كائنًا ثالثًا، فيجب أن يكونا متساويين لبعضهما البعض.
إذاa.equals(b) == true
وa.equals(c) == true
،b.equals(c)
فيجب أيضًا أن يعود صحيحًا. -
إصرار.
يجب أن تتغير نتيجة
equals()
فقط عند تغيير الحقول المعنية. إذا لم تتغير بيانات الكائنين، فيجبequals()
أن تكون النتيجة هي نفسها دائمًا. -
عدم المساواة مع
null
.بالنسبة لأي كائن،
a.equals(null)
يجب إرجاع خطأ.
هذا ليس مجرد مجموعة من "التوصيات المفيدة"، ولكنه عقد صارم منصوص عليه في وثائق أوراكل
GO TO FULL VERSION