HashSet وأسس مجموعات Set
HashSet وأسس مجموعات Set
تُعدّ Set مجموعةً لا تحتوي على عناصر مكرّرة أبدًا. هذا الضمان الوحيد يُشكّل كل قرار يتعلق بآلية عمل Set داخليًا وطريقة استخدامها. HashSet هي التطبيق الأكثر شيوعًا لـ Set في Java — إذ توفّر عمليات add وcontains وremove في وقت ثابت O(1) بالاستناد إلى جدول تجزئة داخلي.
عقد Set
تمتدّ الواجهة java.util.Set من Collection وتضيف قاعدة واحدة: لا يجوز تعايش عنصرين متساويين في المجموعة. "المساواة" تعني ما تُعيده equals() بقيمة true. لهذا السبب تُعيد add() القيمة false بدلًا من إطلاق استثناء حين تحاول إدراج عنصر مكرّر — يُتجاهَل العنصر ببساطة.
HashSet ترتيب الإدراج ولا أي ترتيب مُرتَّب. إن احتجتَ إلى ترتيب متوقّع، استخدم LinkedHashSet (ترتيب الإدراج) أو TreeSet (ترتيب مُرتَّب) — وكلاهما مُغطّى في دروس قادمة.
آلية عمل HashSet داخليًا
داخليًا، يعتمد HashSet<E> على HashMap<E, Object>. عند استدعاء add(element)، تقوم Java بخطوتين:
- استدعاء
element.hashCode()لتحديد "الدلو" الصحيح في جدول التجزئة. - استدعاء
element.equals(other)مقابل أي عنصر موجود في ذلك الدلو للتحقق من التكرار.
هذه العملية ذات الخطوتين هي السبب في ضرورة صحة كلٍّ من hashCode وequals للكائنات الخاصة بك — لا يكفي أيٌّ منهما بمفرده.
عقد equals وhashCode
ينص عقد Java (الموثّق في Object) على:
- إذا كانت
a.equals(b)تُعيدtrue، فيجب أن تتحققa.hashCode() == b.hashCode(). - العكس غير مشترط: قد يتشارك كائنان نفس رمز التجزئة دون أن يكونا متساويين (تلك هي تصادمات التجزئة، وهي أمر طبيعي).
equals أبدًا، فيُخزَّن كلاهما — ينتهي بك الأمر بـ"مكرّرات" لا تستطيع المجموعة رؤيتها.
إليك فئة تُوضّح التطبيق الصحيح:
مع هذه الفئة، يتعامل HashSet<Point> بصورة صحيحة مع كائنَي Point(1, 2) على أنهما متطابقان:
ما يحدث مع equals وhashCode الافتراضيَّين
يُقارن Object.equals الافتراضي المراجع (نفس عنوان الذاكرة)، ويعتمد Object.hashCode على هوية الكائن. إن لم تُجاوز هذه التوابع، يُعدّ كل كائن فريدًا بصرف النظر عن قيم حقوله:
عمليات Set الشائعة
نظرًا لأن Set يُمثّل المجموعات الرياضية، فإن عمليات الدُفعات القياسية تُقابل مباشرة نظرية المجموعات:
Set.of(...) للمجموعات الصغيرة غير القابلة للتعديل. منذ Java 9 يمكنك كتابة Set.of(1, 2, 3) للحصول على مجموعة مدمجة وغير قابلة للتعديل. إن احتجتَ إلى مجموعة قابلة للتعديل مُملوءة مسبقًا، مرّرها إلى مُنشئ HashSet كما هو موضّح أعلاه.
اختيار تطبيق Set المناسب
- HashSet — O(1) في المتوسط لعمليات الإضافة والبحث والحذف؛ دون ضمان للترتيب. الأفضل لمعظم حالات الاستخدام.
- LinkedHashSet — أداء مماثل لـ
HashSetلكنه يحفظ ترتيب الإدراج. - TreeSet — عمليات O(log n)؛ العناصر مرتّبة دائمًا. مُغطّى في الدرس التالي.
الخلاصة
تفرض Set التفرّد باستدعاء equals وhashCode على كل عنصر. يُطبّق HashSet هذا بجدول تجزئة، ما يمنح أداءً O(1) في المتوسط. القاعدة الحاسمة: إن خزّنتَ كائنات مخصّصة في أي Set (أو كمفاتيح في أي Map)، يجب عليك بالضرورة تجاوز كلٍّ من equals وhashCode بصورة متّسقة — كسر هذا العقد ينتج أخطاء بيانات صامتة يصعب تشخيصها للغاية.