الطرق equals() و hashCode() في Java موجودة في فئة Object. لذا، كل فئة Java تحصل على التنفيذ الافتراضي لـ equals() و hashCode(). في هذه المقالة، سننظر في طرق equals() و hashCode() في Java بتفصيل.
Java equals()
تم تعريف طريقة equals() في فئة Object كالتالي:
public boolean equals(Object obj) {
return (this == obj);
}
وفقًا لوثائق Java حول طريقة equals()، يجب أن تلتزم أي تنفيذ بالمبادئ التالية:
- لأي كائن 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.equals(y)
نفس النتيجة، ما لم يتم تعديل أي من خصائص الكائن التي تستخدم في تنفيذequals()
. - تُعيد طريقة equals() في فئة Object قيمة
true
فقط عندما تكون كلتا المراجعتين تشيران إلى نفس الكائن.
Java hashCode()
من الجيد أن تعرف أن `hashCode()` في Java هو طريقة طبيعية وتُرجع قيمة التجزئة الصحيحة للكائن. وعقد الطرفية العام لطريقة `hashCode()` هو:
- يجب أن تُرجع مقارنات متعددة لـ `hashCode()` نفس قيمة العدد الصحيح نفسه، مالم تم تعديل خاصية الكائن التي يُستخدمها في طريقة `equals()`.
- قيمة تجزئة الكائن يمكن أن تتغير في تشغيلات متعددة لنفس التطبيق.
- إذا كانت كائناتان متساويتان وفقًا لطريقة `equals()`، فإن تجزئتهما يجب أن تكون متماثلة.
- إذا كانت كائناتان غير متساويتان وفقًا لطريقة `equals()`، فلا حاجة لأن تكون تجزئتهما مختلفة. قيمة تجزئتهما قد تكون متساوية أو غير متساوية.
أهمية طريقتي `equals()` و `hashCode()`
تستخدم طريقتي `hashCode()` و `equals()` في Java في تنفيذات قائمة على جدول التجزئة لتخزين واسترجاع البيانات. لقد شرحتها بالتفصيل في كيف يعمل HashMap في جافا؟ يجب أن تتبع تنفيذ طرق `equals()` و `hashCode()` هذه القواعد.
- إذا كان `
o1.equals(o2)
`، فإن `o1.hashCode() == o2.hashCode()
` يجب أن يكون دائمًا `true
`. - إذا كان `
o1.hashCode() == o2.hashCode
` صحيحًا، فهذا لا يعني أن `o1.equals(o2)
` ستكون `true
`.
متى يجب تجاوز طرق equals() و hashCode()؟
عندما نقوم بتجاوز طريقة equals()، فإنه من الضروري تقريبًا تجاوز طريقة hashCode() أيضًا حتى لا يتم انتهاك عقدتهما من قبل تنفيذتنا. يرجى ملاحظة أن برنامجك لن يقذف أي استثناءات إذا تم انتهاك عقدة equals() و hashCode()، إذا كنت لا تخطط لاستخدام الفئة كمفتاح في جدول التجزئة، فلن يحدث أي مشكلة. إذا كنت تخطط لاستخدام الفئة كمفتاح في جدول التجزئة، فإنه يجب تجاوز طريقتي equals() و hashCode(). دعونا نرى ما يحدث عندما نعتمد على التنفيذ الافتراضي لطرق equals() و hashCode() ونستخدم فئة مخصصة كمفتاح للخريطة الهاش.
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// طرق getter و setter
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
Integer value = hm.get(dk);
System.out.println(value);
}
private static Map<DataKey, Integer> getAllData() {
Map<DataKey, Integer> hm = new HashMap<>();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
عند تشغيل البرنامج أعلاه، سيطبع null
. هذا لأن طريقة hashCode() في Object تستخدم للعثور على الحاوية للبحث عن المفتاح. نظرًا لعدم وجود لدينا وصول إلى مفاتيح HashMap ونحن نقوم بإنشاء المفتاح مرة أخرى لاسترداد البيانات، ستلاحظ أن قيم التعريف الهاش لكل من الكائنين مختلفة وبالتالي لا يتم العثور على القيمة.
تنفيذ طرق equals() و hashCode()
يمكننا تعريف تنفيذ خاصة بأساليب equals() و hashCode() الخاصة بنا ولكن إذا لم نقم بتنفيذها بعناية، يمكن أن تحدث مشاكل غريبة أثناء التشغيل. لحسن الحظ، معظم بيئات التطوير المتكاملة في هذه الأيام توفر طرقًا لتنفيذها تلقائيًا وإذا لزم الأمر يمكننا تغييرها وفقًا لاحتياجاتنا. يمكننا استخدام Eclipse لتوليد الأساليب equals() و hashCode() تلقائيًا. هنا تنفيذ أساليب equals() و hashCode() المولدة تلقائيًا.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataKey other = (DataKey) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
لاحظ أن كل من أساليب equals() و hashCode() يستخدمان نفس الحقول للحسابات، بحيث يظل عقدهما صالحًا. إذا قمت بتشغيل البرنامج الاختباري مرة أخرى، سيتم الحصول على الكائن من الخريطة وسيقوم البرنامج بطباعة 10. يمكننا أيضًا استخدام Project Lombok لتوليد أساليب equals و hashCode تلقائيًا.
ما هو اصطدام التجزئة
ببساطة شديدة، تستخدم تطبيقات جداول التجزئة في جافا المنطق التالي لعمليات get و put.
- أولاً، حدد “الجاموس” المستخدم باستخدام كود التجزئة “المفتاح”.
- إذا لم تكن هناك كائنات موجودة في الجاموس بنفس كود التجزئة، ثم أضف الكائن لعملية الوضع وارجع قيمة فارغة لعملية الحصول.
- Sure, here’s your text translated to Arabic:
إذا كانت هناك كائنات أخرى في الدلو ذات نفس رمز التجزئة، يتم استخدام طريقة “المفتاح” المساوي.
- إذا كانت طريقة equals() تعيد قيمة صحيحة وكانت عملية وضع، يتم استبدال قيمة الكائن.
- إذا كانت طريقة equals() تعيد قيمة خاطئة وكانت عملية وضع، يتم إضافة إدخال جديد إلى الدلو.
- إذا كانت طريقة equals() تعيد قيمة صحيحة وكانت عملية الحصول، يتم إرجاع قيمة الكائن.
- إذا كانت طريقة equals() تعيد قيمة خاطئة وكانت عملية الحصول، يتم إرجاع قيمة فارغة.
الصورة أدناه تظهر عناصر دلو HashMap وكيف ترتبط طرق equals() و hashCode() بها. الظاهرة عندما تكون لديك مفاتيح مع نفس رمز التجزئة تسمى تصادم التجزئة. إذا لم تتم تنفيذ طريقة hashCode() بشكل صحيح، ستكون هناك عدد أعلى من تصادمات التجزئة ولن يتم توزيع إدخالات الخريطة بشكل صحيح، مما يتسبب في بطء في عمليات الحصول والوضع.
ماذا لو لم نقم بتنفيذ كل من hashCode() و equals()؟
لقد رأينا بالفعل أعلاه أنه إذا لم يتم تنفيذ hashCode()، فإننا لن نتمكن من استرجاع القيمة لأن HashMap يستخدم رمز التجزئة للعثور على الحاوية للبحث عن الإدخال. إذا استخدمنا فقط hashCode() ولم نقم بتنفيذ equals()، فإن القيمة أيضًا لن تتم استرجاعها لأن طريقة equals() ستعيد قيمة false.
أفضل الممارسات لتنفيذ طريقة equals() وطريقة hashCode()
- استخدام نفس الخصائص في تنفيذي طريقتي equals() و hashCode()، بحيث لا ينتهك عقدهما عند تحديث أي خصائص.
- من الأفضل استخدام كائنات لا يمكن تغييرها كمفتاح للجدول الخاص بنا بحيث يمكننا تخزين قيمة رمز التجزئة بدلاً من حسابها في كل مكال؛ ولهذا السبب يُعتبر السلسلة نموذجًا جيدًا لمفتاح الجدول لأنها لا تتغير وتخزن قيمة رمز التجزئة.
- قم بتنفيذ طريقة hashCode() بحيث يحدث أقل عدد من التصادمات ويتم توزيع الإدخالات بشكل متساوٍ عبر جميع الحاويات.
يمكنك تحميل الشيفرة الكاملة من مستودع جيت هاب الخاص بنا.
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode