Java Cha Challeners # 4: مقارنة الكائنات بالتساوي () و hashCode ()

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) { // 1 if (this == o) { return true; } // 2 if (o == null || getClass() != o.getClass()) { return false; } // 3 Simpson simpson = (Simpson) o; return age == simpson.age && weight == simpson.weight && name.equals(simpson.name); } } } 

دعونا نلقي نظرة على طريقة 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() ...
truetrue أم false
falsefalse

عندما ترجع الطريقة equals() إلى true ، فهذا يعني أن الكائنات متساوية في جميع القيم والسمات . في هذه الحالة ، يجب أن تكون مقارنة رمز التجزئة صحيحة أيضًا.


الجدول 3. مقارنة الكائنات مع يساوي ()


عندما ترجع طريقة equals() ...يجب أن ترجع طريقة hashCode() ...
truetrue
falsetrue أم 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 مارس

Source: https://habr.com/ru/post/ar443710/


All Articles