HashMap بعمق
HashMap بعمق
HashMap هي التطبيق الأكثر استخدامًا لواجهة Map في Java. تخزّن البيانات كـأزواج مفتاح-قيمة، وتوفّر بحثًا بمعدل O(1) في المتوسط بالمفتاح، وهي الأداة المناسبة كلّما احتجت إلى ربط شيء بشيء آخر — اسم مستخدم بملف شخصي، أو كلمة بتكرارها، أو معرّف منتج بسعره.
واجهة Map
قبل التعامل مع HashMap مباشرةً، افهم ما تعِده واجهة Map. على خلاف List أو Set، فإنّ Map ليست Collection — لها تسلسل هرمي مستقل. يقول العقد:
- كل مفتاح يرتبط بقيمة واحدة فقط.
- المفاتيح فريدة؛ وضع المفتاح نفسه مرّتين يستبدل القيمة القديمة.
- القيم لا يشترط أن تكون فريدة — يمكن لمفاتيح متعددة أن تشير إلى القيمة ذاتها.
- يمكن أن يرتبط مفتاح بـ
null، كما أنnullنفسه مفتاح صالح فيHashMap.
Map<K,V> من Collection<E>. لا يمكنك تمرير Map حيث تُتوقّع Collection. غير أنه يمكنك استدعاء map.values() أو map.keySet() أو map.entrySet() للحصول على طرق عرض كمجموعات.
إنشاء HashMap
أعلِن دائمًا بنوع الواجهة على اليسار حتى تتمكّن من تبديل التطبيقات لاحقًا:
يتيح عامل الماس <> للمُترجم استنتاج الوسيطات. يمكنك أيضًا ملء الخريطة عند الإنشاء في Java 9+:
العمليات الأساسية: put و get و remove
put(key, value) تضيف إدخالًا أو تستبدله وتُعيد القيمة السابقة (أو null إن كان المفتاح غائبًا). get(key) تُعيد القيمة أو null. remove(key) تحذف الإدخال وتُعيد القيمة المحذوفة.
stock.get("grapes") تُعيد null. تعيينها إلى متغيّر من النوع البدائي int (بدلًا من Integer) يُسبّب NullPointerException. استخدم getOrDefault أو فحص صريح عند احتمال غياب المفتاح.
الوصول الآمن: getOrDefault و computeIfAbsent
getOrDefault(key, fallback) تُعيد القيمة الاحتياطية بدلًا من null عند غياب المفتاح. أما computeIfAbsent فهي الأسلوب الاصطلاحي لبناء قيمة عند الحاجة فقط — مثالية للتجميع:
تفرّد المفاتيح و equals / hashCode
تستخدم HashMap دالة hashCode() للمفتاح لاختيار دلو (bucket)، ثم equals() لتأكيد الهوية. هذا يعني:
- كائنان متساويان بـ
equals()يجب أن يكون لهما نفسhashCode(). - إذا تجاوزت
equals()في فئة مخصصة دون تجاوزhashCode()، ستُنشئ الخريطة مفاتيح مكررة بصمت. - يجب ألّا يتغيّر المفتاح بعد إدراجه — تعديل المفتاح يكسر البحث.
التكرار عبر الإدخالات
توجد ثلاث طرق شائعة للتكرار عبر HashMap. الأنظف هي entrySet() التي تعطيك المفتاح والقيمة معًا دون بحث ثانٍ:
HashMap ترتيب الإدراج. إن احتجت ترتيبًا متوقّعًا استخدم LinkedHashMap (ترتيب الإدراج) أو TreeMap (مرتّب بالمفتاح) — كلاهما مشمول في الدرس التالي.
الأداء والسعة الابتدائية
داخليًا، تخزّن HashMap الإدخالات في مصفوفة من الدلاء. حين يتجاوز عدد الإدخالات capacity * loadFactor (الافتراضي 0.75)، تُعيد التجزئة — تخصّص مصفوفة أكبر وتوزّع جميع الإدخالات من جديد. إعادة التجزئة تكلف O(n) وهي مكلفة. إن كنت تعرف الحجم التقريبي مسبقًا، مرّره في المنشئ لتجنّب إعادة التجزئة:
putIfAbsent و merge
طريقتان أخريان تستحقان المعرفة قبل المتابعة:
putIfAbsent(key, value)— يُدرج فقط إذا كان المفتاح غائبًا؛ يُعيد القيمة الموجودة في حال وجودها.merge(key, value, remappingFn)— إذا كان المفتاح غائبًا يُدرج القيمة؛ وإن كان موجودًا يطبّق الدالة لدمج القيمة القديمة والجديدة. مفيد جدًا للتجميع.
الخلاصة
HashMap هي خزّان المفتاح-القيمة الرئيسي في إطار مجموعات Java. استخدم put/get/remove للعمليات الأساسية، وgetOrDefault وcomputeIfAbsent للوصول الآمن وبناء الهياكل المتداخلة، وentrySet() مع for-each أو lambda forEach للتكرار. تذكّر أن تفرّد المفاتيح يُطبَّق عبر equals() وhashCode() — تجاوزهما دائمًا معًا في فئات المفاتيح المخصصة.