يمكن القول أن أساليب hashcode () و equals () هي ميزة رئيسية في جافا الموجهة نحو الكائنات بالكامل. إنه يسهل برامجنا ويجلب أيضًا الكثير من الأخطار. في هذه المقالة ، سنناقش كيفية فهم واستخدام هاتين الطريقتين بشكل صحيح.
إذا قررت إعادة كتابة طريقة متساوية () ، فيجب أن تكون واضحًا بشأن المخاطر التي تطرحها القيام بذلك والتأكد من أنه يمكنك كتابة طريقة () قوية. شيء واحد يجب أن تلاحظه هو أنه بعد إعادة كتابة متساوٍ () ، يجب إعادة كتابة طريقة HashCode (). سيتم شرح الأسباب المحددة لاحقًا.
دعنا أولاً نلقي نظرة على وصف طريقة equals () في مواصفات Javase 7:
・ إنه انعكاسي: بالنسبة لأي قيمة مرجعية غير خالية x, x.equals(x) يجب أن تعود true .
・ إنه متماثل: بالنسبة لأي قيم مرجعية غير فنية x و y, x.equals(y) يجب أن تُرجع true إذا وفقط إذا كان y.equals(x) يرجع true .
・ إنه متعدّل: بالنسبة لأي قيم مرجعية غير فنية x, y, و z ، إذا كانت x.equals(y) إرجاع true و y.equals(z) إرجاع true ، ثم x.equals(z) يجب أن تعود true.
・ إنه متسق: لأي قيم مرجعية غير خالية x و y ، يتم تعديل دعوات متعددة لـ x.equals(y) باستمرار true أو إرجاعها بشكل false ، لا شريطة أي معلومات تستخدم في مقارنات متساوية على الكائنات.
・ بالنسبة لأي قيمة مرجعية غير فنية x, x.equals(null) false .
يستخدم هذا المقطع الكثير من الأعداد في الرياضيات المنفصلة. اسمحوا لي أن أقدم تفسيرًا موجزًا:
1. الانعكاسية: A.Equals (أ) يجب أن تعود صحيح.
2. التماثل: إذا كان A.equals (ب) يرجع صحيحًا ، فإن B.Equals (A) يحتاج أيضًا إلى العودة إلى حد ما.
3. الإرسال: إذا كان A.equals (B) صحيحًا و B.equals (C) صحيح ، فيجب أن يكون A.Equals (C) صحيحًا أيضًا. لوضعها بصراحة ، a = b ، b = c ، ثم a = c.
4. الاتساق: طالما أن حالة الكائنات A و B لا تتغير ، يجب أن تعود A.equals (ب) دائمًا.
5. أ.
أعتقد أنه طالما أن الأشخاص الذين ليسوا محترفين في الرياضيات لن يطلقوا على الأشياء المذكورة أعلاه. في التطبيق الفعلي ، نحتاج فقط إلى إعادة كتابة طريقة متساوية () وفقًا لبعض الخطوات. لراحة التفسير ، نقوم أولاً بتحديد فئة المبرمج (المبرمج):
CLASS CODER {Private String Name ؛ عصر INT الخاص ؛ // getters and setters}ما نريده هو أنه إذا كانت أسماء وأعمار كائدي المبرمجين متماثلين ، فإننا نعتقد أن هذين المبرمجين متماثلان. في هذا الوقت ، علينا إعادة كتابة طريقة متساوية (). لأن الافتراضي يساوي () في الواقع يحدد ما إذا كان مرجعان يشيران إلى نفس الكائن جوهري ، فهو يعادل ==. عند إعادة الكتابة ، اتبع الخطوات الثلاث التالية:
1. تحديد ما إذا كان مساوياً لنفسك.
إذا (الآخر == هذا) العودة صحيح ؛
2. استخدم مشغل مثيل OF لتحديد ما إذا كان الآخر هو كائن من نوع المبرمج.
إذا (! (مثيل آخر من المبرمج)) إرجاع خطأ ؛
3. قارن مجالات البيانات والاسم والعمر الذي تقوم بتخصيصه في فئة المبرمج ، ويجب ألا تفوت واحدة.
المبرمج O = (المبرمج) الآخر ؛ return o.name.equals (name) && o.age == Age ؛
رؤية هذا ، قد يسأل شخص ما ، هناك ممثلون في الخطوة 3. إذا مر شخص ما كائن من فئة عدد صحيح في هذا المساواة ، فهل يرمي classcastexception؟ هذا القلق هو في الواقع زائدة عن الحاجة. نظرًا لأننا أصدرنا حكم مثيل OF في الخطوة الثانية ، إذا كان الآخر هو كائن غير مرئي ، أو حتى غيره ، فسيتم إرجاع FALSE مباشرة في هذه الخطوة ، حتى لا تتاح للرمز اللاحق تنفيذها.
الخطوات الثلاث المذكورة أعلاه هي أيضًا الخطوات الموصى بها في <java> الفعالة ، والتي يمكن أن تضمن أساسًا عدم وجود خطأ.
في مواصفات Javase 7 ،
"لاحظ أنه من الضروري عمومًا تجاوز طريقة Hashcode كلما تم تجاوز هذه الطريقة (متساوية) ، وذلك للحفاظ على العقد العام لطريقة هش رمز ، والتي تنص على أن الكائنات المتساوية يجب أن يكون لها رموز تجزئة متساوية."
إذا قمت بإعادة كتابة طريقة متساوية () ، فاحلل إعادة كتابة طريقة hashcode (). لقد تعلمنا جداول التجزئة في دورات هيكل بيانات الكمبيوتر الجامعي. طريقة hashcode () تخدم جدول التجزئة.
عندما نستخدم فئة تجميع تبدأ بـ Hash مثل التجزئة ، مثل HashMap و Hashset ، سيتم استدعاء HashCode () ضمنيًا لإنشاء علاقة تعيين التجزئة. سنشرح هذا لاحقًا. سنركز هنا على كتابة طريقة hashcode () أولاً.
<fective java> يوفر طريقة للكتابة التي يمكن أن تتجنب تعارضات التجزئة إلى أقصى حد ، لكنني شخصياً أعتقد أنه ليس من الضروري القيام بالكثير من المتاعب للتطبيقات العامة. إذا كنت بحاجة إلى تخزين عشرات الآلاف أو الملايين من الكائنات في التطبيق الخاص بك ، فيجب عليك اتباع بشكل صارم الأساليب الواردة في الكتاب. إذا كنت تكتب تطبيقًا صغيرًا ومتوسطًا ، فإن المبادئ التالية كافية:
من الضروري التأكد من أن جميع أعضاء كائن المبرمج يمكن أن ينعكس في Hashcode.
على سبيل المثال ، يمكننا كتابة هذا:
Override public int hashcode () {int result = 17 ؛ النتيجة = النتيجة * 31 + name.hashCode () ؛ النتيجة = النتيجة * 31 + العمر ؛ نتيجة العودة }عندما تكون النتيجة int = 17 ، يمكنك أيضًا تغييرها إلى 20 ، 50 ، وما إلى ذلك. رؤية هذا ، كنت فضوليًا فجأة وأردت أن أرى كيف يتم تنفيذ طريقة HashCode () في فئة السلسلة. تحقق من الوثائق ومعرفة:
"إرجاع رمز التجزئة لهذه السلسلة. يتم حساب رمز التجزئة لكائن سلسلة
S [0]*31^(n-1) + s [1]*31^(n-2) + ... + s [n-1]
باستخدام int arithmetic ، حيث S [i] هو حرف ITH للسلسلة ، n هو طول السلسلة ، و ^ يشير إلى الأسس. (قيمة التجزئة للسلسلة الفارغة هي صفر.) "
احسب رمز ASCII لكل حرف إلى Power N - 1 ثم أضفه. يمكن ملاحظة أن الشمس صارمة للغاية في تنفيذ Hashcode. هذا يمكن أن يتجنب نفس الرمز في السلاسل المختلفة إلى أقصى حد.
تتم الإشارة إلى مفهوم دلو في تطبيق جدول Oracle. كما هو مبين في الشكل أدناه:
كما يتضح من الشكل أعلاه ، فإن جدول التجزئة مع دلو يعادل تقريبًا مجموعة من جدول التجزئة وقائمة مرتبطة. أي أنه سيتم تعليق القائمة المرتبطة على كل دلو ، وسيتم استخدام كل عقدة من القائمة المرتبطة لتخزين الكائنات. تستخدم Java طريقة HashCode () لتحديد الدلو الذي يجب أن يكون كائنًا ، ثم يبحث عنه في القائمة المرتبطة المقابلة. من الناحية المثالية ، إذا كانت طريقة HashCode () مكتوبة قوية بما يكفي ، فسيحتوي كل دلو على عقدة واحدة فقط ، والتي ستحقق التعقيد الزمني على المستوى الثابت لعملية البحث. أي أنه بغض النظر عن جزء من الذاكرة التي يتم وضعها في الكائن ، يمكنني تحديد موقع المنطقة على الفور من خلال HashCode () دون عبورها والبحث من البداية إلى النهاية. هذا هو أيضا التطبيق الرئيسي لجداول التجزئة.
يحب:
عندما نسمي طريقة put (Object O) لشركة Hashset ، سنقوم أولاً بتحديد موقعها في الدلو المقابل وفقًا لقيمة إرجاع O.HashCode (). إذا لم تكن هناك عقد في الدلو ، فضع O هنا. إذا كانت هناك عقد بالفعل ، ثم شنق O إلى نهاية القائمة المرتبطة. وبالمثل ، عندما يحتوي الاتصال على (كائن O) ، ستقوم Java بتحديد موقع الدلو المقابل من خلال قيمة الإرجاع لـ HashCode () ، ثم اتصل بالطريقة المتساوية () بدورها في العقد في القائمة المرتبطة المقابلة لتحديد ما إذا كان الكائن الموجود في العقدة هو الكائن الذي تريده.
دعنا نستخدم مثالًا لتجربة هذه العملية:
دعنا ننشئ كائنين مشفرين جديدين أولاً:
المبرمج C1 = المبرمج الجديد ("Bruce" ، 10) ؛ المبرمج C2 = المبرمج الجديد ("Bruce" ، 10) ؛افترض أننا قمنا بإعادة كتابة طريقة equals () للمبرمج دون إعادة كتابة طريقة hashcode ():
Override public boolean يساوي (كائن آخر) {system.out.println ("equals method voked!") ؛ إذا (الآخر == هذا) العودة صحيح ؛ إذا (! (مثيل آخر من المبرمج)) إرجاع خطأ ؛ المبرمج O = (المبرمج) الآخر ؛ return o.name.equals (name) && o.age == Age ؛ }ثم نقوم ببناء مجموعة من التجزئة ونضع كائن C1 في المجموعة:
SET <CODER> SET = NEW HASHSET <CODER> () ؛ set.add (c1) ؛
تنفيذ مرة أخرى:
System.out.println (set.contains (c2)) ؛
نتوقع أن تعود طريقة تحتوي على (C2) بشكل صحيح ، لكنها في الواقع تعود خاطئة.
اسم وعمر C1 و C2 هو نفسه. لماذا أتصل يحتوي على (C2) وإرجاع خطأ بعد وضع C1 في hashset؟ هذا هو hashcode () الذي يسبب المتاعب. نظرًا لأنك لم تعيد كتابة طريقة HashCode () ، عندما تبحث Hashset عن C2 ، فإنها ستبحث عنها في دلاء مختلفة. على سبيل المثال ، إذا تم وضع C1 في الدلو 05 ، يتم البحث في الدلو 06 عند البحث عن C2 ، لذلك لا يمكن العثور عليه بالطبع. لذلك ، فإن الغرض من إعادة كتابة Hashcode () هو أنه عندما يعود A.equals (ب) صحيح ، يجب أن يعيد HashCode () A و B نفس القيمة.
هل أطلب من hashcode () إرجاع خط رقم ثابت في كل مرة
قد يعيدها شخص ما مثل هذا:
Override public int hashcode () {return 10 ؛ }إذا كان هذا هو الحال ، فإن HashMap و Hashset وفصول التجميع الأخرى ستفقد "معنى التجزئة". على حد تعبير <java> ، يتدهور جدول التجزئة إلى قائمة مرتبطة. إذا قام HashCode () بإرجاع نفس الرقم في كل مرة ، فسيتم وضع جميع الكائنات في نفس الدلو ، وفي كل مرة تقوم فيها بإجراء عملية بحث ، فإنها ستجتاز القائمة المرتبطة ، والتي ستفقد وظيفة التجزئة تمامًا. لذلك من الأفضل توفير Hashcode () كأفكرة جيدة.
ما ورد أعلاه هو كل المقدمة التفصيلية لهذه المقالة حول إعادة كتابة الأساليب Hashcode () و equals (). آمل أن يكون ذلك مفيدًا للجميع. يمكن للأصدقاء المهتمين الاستمرار في الرجوع إلى الموضوعات الأخرى ذات الصلة على هذا الموقع. إذا كانت هناك أي أوجه قصور ، فيرجى ترك رسالة لإشارةها. شكرا لك يا أصدقائك لدعمكم لهذا الموقع!