Java Cha Challeners # 4: مقارنة الكائنات بالتساوي () و hashCode ()
تحسبا لإطلاق سلسلة رسائل جديدة في الدورة التدريبية "Java Developer" ، نواصل ترجمة سلسلة من مقالات Java Cha Challeners ، يمكن قراءة الأجزاء السابقة منها على الروابط أدناه:
دعنا نذهب!
في هذه المقالة ، سوف تتعلم كيف ترتبط طرق equals() و hashCode() وكيف يتم استخدامها عند مقارنة الكائنات.

بدون استخدام equals() و hashCode() لمقارنة حالة كائنين ، نحتاج إلى كتابة الكثير من المقارنات " if " التي تقارن كل مجال من مجالات الكائن. هذا النهج يجعل الشفرة مربكة ويصعب قراءتها. العمل معًا ، تساعد هاتان الطريقتان على إنشاء رمز أكثر مرونة وثباتًا.
شفرة المصدر لهذه المادة هنا .
تجاوز يساوي () و hashCode ()
تجاوز الأسلوب عبارة عن تقنية يتم من خلالها إعادة كتابة سلوك فئة أو واجهة أصل (إعادة تعريف) في فئة فرعية (انظر Java Cha Challeners # 3: تعدد الأشكال والميراث ، المهندس. ). في Java ، كل كائن له طرق equals() و hashCode() ، ولكي يعمل بشكل صحيح ، يجب تجاوزها.
لفهم كيفية عمل إعادة تعريف equals() و hashCode() ، دعونا نفحص تطبيقها في فئات Java الأساسية. التالي هو طريقة equals() فئة Object . يتحقق الأسلوب مما إذا كان المثيل الحالي يطابق كائن obj تم تمريره.
public boolean equals(Object obj) { return (this == obj); }
الآن دعونا نلقي نظرة على طريقة hashCode() في فئة Object .
@HotSpotIntrinsicCandidate public native int hashCode();
هذا أصلي - وهي طريقة مكتوبة بلغة أخرى ، مثل C ، وتقوم بإرجاع بعض الكود الرقمي المرتبط بعنوان ذاكرة الكائن. (إذا كنت لا تكتب كود JDK ، فليس من المهم أن تعرف بالضبط كيف تعمل هذه الطريقة.)
ملاحظة المترجم: القيمة المرتبطة بالعنوان غير صحيحة تمامًا ( بفضل vladimir_dolzhenko ). يستخدم HotSpot JVM أرقام عشوائية زائفة بشكل افتراضي. وصف تطبيق hashCode () لـ HotSpot موجود هنا وهنا .
إذا لم يتم تجاوز أساليب equals() و hashCode() ، فسيتم استدعاء أساليب فئة Object الموضحة أعلاه بدلاً من ذلك. في هذه الحالة ، لا تفي الطرق بالغرض الحقيقي من equals() و hashCode() ، وهو التحقق مما إذا كانت الكائنات لها نفس الحالة.
بشكل عام ، تجاوز equals() يتجاوز أيضًا hashCode() .
مقارنة الكائنات بالتساوي ()
يتم استخدام الأسلوب equals() لمقارنة الكائنات. لتحديد ما إذا كانت الكائنات متطابقة أم لا ، يقارن equals() قيم حقل الكائن:
public class EqualsAndHashCodeExample { public static void main(String... args){ System.out.println(new Simpson("Homer", 35, 120) .equals(new Simpson("Homer",35,120))); System.out.println(new Simpson("Bart", 10, 120) .equals(new Simpson("El Barto", 10, 45))); System.out.println(new Simpson("Lisa", 54, 60) .equals(new Object())); } static class Simpson { private String name; private int age; private int weight; public Simpson(String name, int age, int weight) { this.name = name; this.age = age; this.weight = weight; } @Override public boolean equals(Object o) {
دعونا نلقي نظرة على طريقة equals() . المقارنة الأولى تقارن المثيل الحالي this بمرور o . إذا كان هو نفس الكائن ، فسوف تعاد equals() true .
تتحقق المقارنة الثانية لمعرفة ما إذا كان الكائن الذي تم تمريره null وما نوعه. إذا كان الكائن المنقول من نوع مختلف ، فلا تكون الكائنات متساوية.
أخيرًا ، يقارن equals() حقول الكائنات. إذا كان لكائنين نفس قيم الحقل ، فستكون الكائنات متماثلة.
تحليل الخيارات لمقارنة الكائنات
الآن دعونا نلقي نظرة على خيارات لمقارنة الكائنات في الطريقة main() . أولاً ، نقارن بين كائنين في Simpson :
System.out.println( new Simpson("Homer", 35, 120).equals( new Simpson("Homer", 35, 120)));
تحتوي حقول هذه الكائنات على نفس القيم ، وبالتالي ستكون النتيجة true .
ثم قارن مرة أخرى بين كائنين Simpson :
System.out.println( new Simpson("Bart", 10, 45).equals( new Simpson("El Barto", 10, 45)));
الكائنات هنا متشابهة ، لكن معاني الأسماء مختلفة: Bart و El Barto . لذلك ، ستكون النتيجة false .
أخيرًا ، دعنا نقارن كائن Simpson ومثيل فئة Object :
System.out.println( new Simpson("Lisa", 54, 60).equals( new Object()));
في هذه الحالة ، ستكون النتيجة false ، لأن أنواع الكائنات مختلفة.
يساوي () مقابل ==
للوهلة الأولى ، يبدو أن عامل == وطريقة equals() يقومان بنفس الشيء ، لكن في الواقع ، يعملان بشكل مختلف. يقارن العامل == ما إذا كان هناك ارتباطان يشيران إلى نفس الكائن. على سبيل المثال:
Simpson homer = new Simpson("Homer", 35, 120); Simpson homer2 = new Simpson("Homer", 35, 120); System.out.println(homer == homer2);
أنشأنا حالتين مختلفتين من Simpson باستخدام المشغل new . لذلك ، سوف تشير المتغيرات homer و homer2 إلى كائنات مختلفة في الكومة . وبالتالي ، نتيجة لذلك ، نحن false .
في المثال التالي ، نستخدم طريقة equals() overridden equals() :
System.out.println(homer.equals(homer2));
في هذه الحالة ، سيتم مقارنة الحقول. ونظرًا لأن كلاً من كلاً من Simpson لهما قيم الحقل نفسها ، ستكون النتيجة true .
تحديد الكائنات مع hashCode ()
لتحسين الأداء عند مقارنة الكائنات ، يتم استخدام طريقة hashCode() . إرجاع الأسلوب hashCode() معرف فريد لكل كائن ، مما يبسط المقارنة بين حالات الكائن.
إذا لم يتطابق رمز تجزئة كائن مع رمز تجزئة لكائن آخر ، فيمكنك حذف طريقة equals() : أنت تعرف تمامًا أن كائنين لا يتطابقان. من ناحية أخرى ، إذا كان رمز التجزئة هو نفسه ، فأنت بحاجة إلى تنفيذ طريقة equals() لتحديد ما إذا كانت قيم الحقل متطابقة.
النظر في مثال عملي مع hashCode() .
public class HashcodeConcept { public static void main(String... args) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println(" equals."); } else { System.out.println(" equals, .. " + " , , ."); } } static class Simpson { int id; String name; public Simpson(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Simpson simpson = (Simpson) o; return id == simpson.id && name.equals(simpson.name); } @Override public int hashCode() { return id; } } }
طريقة hashCode() ، والتي تُرجع دائمًا نفس القيمة ، صالحة لكنها غير فعالة. في هذه الحالة ، ستعود المقارنة دائمًا إلى true ، لذلك سيتم دائمًا تنفيذ طريقة equals() . في هذه الحالة ، لا يوجد أي تحسن في الأداء.
باستخدام يساوي () و hashCode () مع المجموعات
يجب أن تمنع الفئات التي تنفذ واجهة Set (set) من إضافة العناصر المكررة. فيما يلي بعض الفئات التي تنفذ واجهة Set :
يمكن إضافة العناصر الفريدة فقط إلى Set . وبالتالي ، إذا كنت ترغب في إضافة عنصر ، على سبيل المثال ، إلى HashSet ، فيجب عليك أولاً استخدام أساليب equals() و hashCode() للتأكد من أن هذا العنصر فريد من نوعه. إذا لم يتم تجاوز hashCode() equals() و hashCode() ، فإنك تخاطر بإدراج قيم مكررة.
لنلقِ نظرة على جزء التنفيذ لطريقة add() في HashSet :
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e;
قبل إضافة عنصر جديد ، يتحقق HashSet لمعرفة ما إذا كان العنصر موجودًا في هذه المجموعة. إذا تطابق الكائن ، فلن يتم إدراج العنصر الجديد.
يتم استخدام أساليب equals() و hashCode() ليس فقط في Set . هذه الطرق مطلوبة أيضًا لـ HashMap و Hashtable و LinkedHashMap . كقاعدة عامة ، إذا رأيت مجموعة تشتمل على بادئة "تجزئة" ، فيمكنك التأكد من أنه من أجل تشغيلها الصحيح ، يجب تجاوز طرق hashCode() وطرق equals() .
توصيات لاستخدام يساوي () و hashCode ()
قم بتشغيل الأسلوب equals() فقط للكائنات التي لها نفس رمز التجزئة. لا تقم بتنفيذ equals() إذا كان رمز التجزئة مختلفًا.
الجدول 1. مقارنة رمز التجزئة
| إذا كان hashCode () المقارنة ... | هذا ... |
|---|
يعود true | تنفيذ equals() |
إرجاع false | لا تنفذ equals() |
يستخدم هذا المبدأ بشكل أساسي في مجموعات Set أو Hash لأسباب تتعلق بالأداء.
قواعد مقارنة الكائن
عندما ترجع المقارنة hashCode() false ، يجب أن ترجع الطريقة equals() أيضًا false . إذا كان رمز التجزئة مختلفًا ، فالكائنات بالتأكيد ليست متساوية.
جدول 2. مقارنة الكائنات مع hashCode ()
عندما ترجع المقارنة hashCode() ... | يجب أن ترجع الطريقة equals() ... |
|---|
true | true أم false |
false | false |
عندما ترجع الطريقة equals() إلى true ، فهذا يعني أن الكائنات متساوية في جميع القيم والسمات . في هذه الحالة ، يجب أن تكون مقارنة رمز التجزئة صحيحة أيضًا.
الجدول 3. مقارنة الكائنات مع يساوي ()
عندما ترجع طريقة equals() ... | يجب أن ترجع طريقة hashCode() ... |
|---|
true | true |
false | true أم false |
حل المشكلة على يساوي () و hashCode ()
حان الوقت لاختبار معرفتك بأساليب equals() و hashCode() . المهمة هي معرفة نتيجة عدة equals() والحجم الكلي لمجموعة Set .
للبدء ، ادرس الكود التالي بعناية:
public class EqualsHashCodeChallenge { public static void main(String... args) { System.out.println(new Simpson("Bart").equals(new Simpson("Bart"))); Simpson overriddenHomer = new Simpson("Homer") { public int hashCode() { return (43 + 777) + 1; } }; System.out.println(new Simpson("Homer").equals(overriddenHomer)); Set set = new HashSet(Set.of(new Simpson("Homer"), new Simpson("Marge"))); set.add(new Simpson("Homer")); set.add(overriddenHomer); System.out.println(set.size()); } static class Simpson { String name; Simpson(String name) { this.name = name; } @Override public boolean equals(Object obj) { Simpson otherSimpson = (Simpson) obj; return this.name.equals(otherSimpson.name) && this.hashCode() == otherSimpson.hashCode(); } @Override public int hashCode() { return (43 + 777); } } }
أولاً ، قم بتحليل الكود ، فكر في النتيجة. وعندها فقط قم بتشغيل الكود. الهدف هو تحسين مهارات تحليل الشفرة الخاصة بك وتعلم مفاهيم Java الأساسية بحيث يمكنك تحسين الشفرة.
ماذا ستكون النتيجة؟
A) true true 4 B) true false 3 C) true false 2 D) false true 3
ماذا حدث فهم يساوي () و hashCode ()
في المقارنة الأولى ، تكون نتيجة equals() true ، لأن حالات الكائنات متشابهة ، وطريقة hashCode() تُرجع نفس القيمة لكلا الكائنين.
في المقارنة الثانية ، تم تجاوز أسلوب hashCode() للمتغير hashCode() . بالنسبة لكلا Simpson ، يكون الاسم "Homer" ، ولكن بالنسبة overriddenHomer فإن طريقة hashCode() تُرجع قيمة مختلفة. في هذه الحالة ، ستكون نتيجة طريقة equals() false ، لأنها تحتوي على مقارنة برمز التجزئة.
يجب أن تدرك أنه سيكون هناك ثلاثة كائنات Simpson في المجموعة. دعونا نجعلها خارج.
سيتم إدراج الكائن الأول في المجموعة كالمعتاد:
new Simpson("Homer");
سيتم أيضًا إدراج الكائن التالي بالطريقة المعتادة ، لأنه يحتوي على قيمة مختلفة عن الكائن السابق:
new Simpson("Marge");
أخيرًا ، كائن Simpson التالي له نفس قيمة اسم الكائن الأول. في هذه الحالة ، لن يتم إدراج الكائن:
set.add(new Simpson("Homer"));
كما نعلم ، يستخدم الكائن overridenHomer قيمة تجزئة مختلفة ، على عكس مثيل Simpson("Homer") العادي Simpson("Homer") . لهذا السبب ، سيتم إدراج هذا العنصر في المجموعة:
set.add(overriddenHomer);
الجواب
الجواب الصحيح هو B. الخلاصة ستكون:
true false 3
الأخطاء الشائعة مع يساوي () و hashCode ()
- عدم وجود تجاوز
hashCode() مع تجاوز تجاوز equals() أو العكس. - عدم وجود تجاوز
equals() و hashCode() عند استخدام مجموعات التجزئة مثل HashSet . - إرجاع قيمة ثابتة في طريقة
hashCode() بدلاً من إرجاع رمز فريد لكل كائن. - الاستخدام المكافئ لـ
== و equals() . يقارن العامل == مراجع الكائنات ، بينما يقارن الأسلوب equals() قيم الكائن.
ما تحتاج إلى تذكره حول يساوي () و hashCode ()
- يوصى دائمًا بتجاوز أساليب
equals() و hashCode() في POJOs ( الروسية والإنجليزية ) - استخدم خوارزمية فعالة لإنشاء رمز تجزئة فريد.
- عند تجاوز الأسلوب
equals() ، تجاوز دائمًا طريقة hashCode() . - يجب أن تقارن طريقة
equals() حالة الكائنات الكاملة (القيم من الحقول). - يمكن أن يكون أسلوب
hashCode() معرف POJO (ID). - إذا كانت نتيجة مقارنة رمز التجزئة للكائنين
false ، فيجب أن تكون طريقة equals() false أيضًا. - إذا لم
hashCode() إعادة تعريف equals() و hashCode() عند استخدام مجموعات hash ، فسيكون للمجموعة عناصر مكررة.
تعلم المزيد عن جافا
تقليديا ، أنا في انتظار تعليقاتكم وأدعوك إلى درس مفتوح ، والذي سيعقد من قبل معلمنا سيرجي Petrelevich في 18 مارس