المجموعة تخزّن القيم الفريدة
HashSet (من java.util) هي مجموعة تحتفظ بكل قيمة مرة واحدة على الأكثر. لا توجد مفاتيح ولا قيم مقترنة بها كما في HashMap - مجرد كيس من العناصر المتمايزة. وظيفتها الوحيدة هي الإجابة بسرعة عن سؤال واحد: «هل هذا الشيء موجود هنا؟»
هناك معامل نوع واحد، <ElementType>. كما هو الحال مع ArrayList وHashMap، تُحدّد عادةً نوع المتغيّر بـواجهة Set وتُنشئ HashSet.
add يُرجِع ما إذا كانت القيمة جديدة
لا يكتفي add بتخزين القيمة - بل يُرجِع قيمة boolean تخبرك بما إذا كانت المجموعة قد تغيّرت فعلًا. إضافة قيمة موجودة مسبقًا تُرجِع false وتترك المجموعة دون تغيير.
قيمة الإرجاع هذه مفيدة حقًا: if (!seen.add(x)) { /* x مكرّر */ } تتيح لك اكتشاف التكرارات في سطر واحد أثناء عملك.
إزالة التكرارات من قائمة
بما أن المجموعة ترفض التكرارات، فإن أسرع طريقة لإزالة التكرارات من مجموعة هي تفريغها داخل واحدة. مُنشئ HashSet يقبل أي مجموعة أخرى:
هذا هو السبب الأكثر شيوعًا الذي يدفع المبتدئين إلى اللجوء إلى مجموعة. فقط اعلم أنك تفقد الترتيب الأصلي في هذه الرحلة ذهابًا وإيابًا - استخدم LinkedHashSet إذا كان الترتيب مهمًا (سنتناوله أدناه).
contains وremove وsize
العمليات اليومية تماثل عمليات المجموعات الأخرى:
الميزة الكبرى مقارنةً بـ ArrayList هي contains. على القائمة أن تمرّ على كل عنصر لتجيب عن ذلك (O(n))؛ أما HashSet فيقفز إلى الإجابة مباشرةً تقريبًا (نحو O(1)). وعندما تجد نفسك تستدعي list.contains(...) داخل حلقة، فهذه عادةً إشارة إلى الانتقال إلى مجموعة.
عمليات المجموعات: الاتحاد والتقاطع والفرق
تتألق المجموعات عند دمجها. تُقرأ التوابع وكأنها لغة عادية بمجرد أن تعرف أيّها يفعل ماذا:
المزلق الأساسي: addAll وretainAll وremoveAll تُعدّل المجموعة التي تُستدعى عليها. ولهذا ينسخ كل مثال a أولًا في HashSet جديد - وإلا فستدمّر مجموعتك الأصلية. أنشئ مجموعة جديدة لكل نتيجة.
HashSet لا يحفظ الترتيب
مثل HashMap، لا يضمن HashSet أي ترتيب للمرور، وقد يختلف الترتيب بين تشغيلٍ وآخر. وإذا احتجت إلى قابلية التنبؤ:
LinkedHashSetيحافظ على ترتيب الإدراج - الترتيب الذي أضفت به العناصر.TreeSetيبقي العناصر مرتَّبة وفق ترتيبها الطبيعي (أو وفقComparatorتزوّده به).
جميعها الثلاثة تُنفّذ واجهة Set، لذا فإن التبديل بينها مجرد تغيير في سطر واحد في المُنشئ.
يجب أن تكون العناصر قابلة للتجزئة (Hashable)
يعتمد HashSet في صميمه على HashMap، لذا تنطبق القاعدة نفسها: فهو يحدّد مواقع العناصر بتجزئتها (hashing)، ما يعني أن hashCode() وequals() للعنصر يجب أن يتّسقا معًا. الأنواع المدمجة مثل String وInteger تفعل ذلك على نحو صحيح أصلًا، ولهذا انطوت سلاسل "java" المكرّرة في المثال أعلاه بشكل صحيح. وإذا خزّنت كائناتٍ من صنفك الخاص، فأعِد تعريف كلٍّ من equals وhashCode - وإلا فسيُعامَل كائنان «متساويان» في المعنى على أنهما متمايزان، وسيفشل contains وإزالة التكرارات بصمت.
التالي: المرور على المجموعات
لقد تعرّفت الآن على المجموعات الثلاث الأساسية - ArrayList وHashMap وHashSet. تمرّ على كلٍّ منها بطريقة مختلفة قليلًا، وهناك مزالق دقيقة (مثل تعديل مجموعة أثناء المرور عليها). سنجمع كل ذلك معًا تاليًا، ونتناول كيفية المرور على المجموعات بأناقة باستخدام حلقة for-each والمكرِّرات (iterators) وforEach.
الأسئلة الشائعة
كيف تُنشئ HashSet في Java؟
أعلِن عنها بمعامل نوع واحد - وهو نوع العنصر - واستدعِ المُنشئ: Set<String> tags = new HashSet<>();. أضف القيم باستخدام tags.add("java"); واختبر العضوية باستخدام tags.contains("java");. واستورد java.util.HashSet وjava.util.Set.
ما الفرق بين HashSet وArrayList في Java؟
يحتفظ ArrayList بكل عنصر تضيفه (بما في ذلك التكرارات) بترتيب الإدراج، ويُفهرَس حسب الموضع. أما HashSet فيخزّن القيم الفريدة فقط، ولا يضمن أي ترتيب، وليس له فهرس، وفحص contains لديه يستغرق زمنًا ثابتًا تقريبًا بدلًا من المرور على القائمة كاملةً. اختر HashSet عندما يهمّك التفرّد أو العضوية السريعة، لا الموضع.
كيف تُزيل التكرارات من قائمة في Java؟
مرّر القائمة إلى مُنشئ HashSet: Set<String> unique = new HashSet<>(list);. تتخلّص المجموعة من القيم المكرَّرة تلقائيًا. وإذا احتجت إلى قائمة مرة أخرى (ولم تمانع فقدان الترتيب)، فغلّفها من جديد: new ArrayList<>(unique). واستخدم LinkedHashSet بدلًا من ذلك إذا أردت الحفاظ على الترتيب الأصلي.