الـ Map يخزّن أزواج المفتاح والقيمة
تخزّن HashMap (من java.util) ارتباطات: كل مفتاح يرتبط بـقيمة واحدة، وتبحث عن القيم عبر مفتاحها في زمن شبه ثابت. تخيّل قاموسًا: الكلمة هي المفتاح والتعريف هو القيمة.
معاملا النوع هما <KeyType, ValueType>. هنا تكون المفاتيح من نوع String والقيم من نوع Integer. كما هو الحال مع ArrayList، عادةً ما تُعلِن عن المتغيّر بنوع الواجهة Map وتُنشئ HashMap.
put وget والكتابة فوق القيمة
أمران ينبغي استيعابهما:
- المفتاح فريد. استدعاء
putبمفتاح موجود يستبدل قيمته ويُعيد القيمة القديمة. - استدعاء
getبمفتاح غير موجود يُعيدnull، وليس خطأً. وفك التغليف التلقائي لهذا الـnullإلىintيُطلِقNullPointerException، وهو مصدر شائع للأخطاء.
getOrDefault يتفادى فخ القيمة null
بدلًا من فحص null في كل مرة، اطلب قيمة افتراضية:
هذه هي أنظف طريقة للتعامل مع عمليات البحث "ربما موجودة"، وهي تقود مباشرةً إلى أشهر نمط لـ HashMap.
عدّ مرات التكرار
عدّ كم مرة يظهر كل عنصر هو المهمة النموذجية لـ HashMap:
يُقرأ النمط map.put(key, map.getOrDefault(key, 0) + 1) على أنه "خذ العدّاد الحالي (أو صفرًا)، أضِف واحدًا، ثم خزّنه مرة أخرى". وهناك مكافئ أكثر أناقة هو counts.merge(word, 1, Integer::sum).
الفحص والحذف
تكتب putIfAbsent(key, value) فقط عندما يكون المفتاح غير موجود، وهي مفيدة للتهيئة الكسولة.
المرور على HashMap
تمرّ الحلقة الأكثر شيوعًا على entrySet()، فتعطيك كل مفتاح وقيمته معًا:
إذا كنت تحتاج إلى المفاتيح فقط أو القيم فقط:
يمكنك أيضًا استخدام forEach مع تعبير لامدا: ages.forEach((name, age) -> System.out.println(name + ": " + age));.
الـ HashMap لا تحافظ على الترتيب
لا تقدّم HashMap أي ضمان بشأن ترتيب المرور على العناصر؛ فهو ما تُنتجه عملية التجزئة، وقد يتغيّر بين عملية تشغيل وأخرى. إذا كنت بحاجة إلى ترتيب يمكن التنبؤ به:
- تحافظ
LinkedHashMapعلى ترتيب الإدخال. - تُبقي
TreeMapالمفاتيح مرتّبة حسب الترتيب الطبيعي (أو حسبComparatorتقدّمه أنت).
تُنفّذ الثلاث جميعها واجهة Map، لذا فإن التبديل بينها هو تغيير في سطر واحد فقط في المُنشئ.
يجب أن تكون المفاتيح قابلة للتجزئة
تجد HashMap العناصر عبر تجزئة المفتاح، لذا يجب أن تتوافق دالتا hashCode() وequals() الخاصتان بالمفتاح فيما بينهما. الأنواع المدمجة مثل String وInteger تفعل ذلك بالفعل بشكل صحيح. وإذا استخدمت صنفًا من إنشائك كمفتاح، فأعِد تعريف كل من equals وhashCode؛ وإلا فإن كائنين "متساويين" في المعنى سينتهيان في حاويات مختلفة، وستفشل عمليات البحث لديك بشكل غامض.
التالي: HashSet
تُجيب HashMap عن السؤال "ما القيمة المخزّنة تحت هذا المفتاح؟". وعندما يهمّك فقط ما إذا كان شيء ما موجودًا أساسًا (مجموعة من القيم الفريدة، دون بيانات مرتبطة)، فإن الأداة المناسبة هي HashSet، وهي موضوعنا التالي.
الأسئلة الشائعة
كيف تُنشئ HashMap في Java؟
أعلِن عنها بمعاملَي نوع (نوع المفتاح ونوع القيمة) واستدعِ المُنشئ: Map<String, Integer> ages = new HashMap<>();. ثم أضِف عناصر باستخدام ages.put("Ada", 36); واقرأها باستخدام ages.get("Ada");. استورد java.util.HashMap وjava.util.Map.
كيف تمر على عناصر HashMap في Java؟
مُرّ على map.entrySet() بحلقة for-each للحصول على كل مفتاح وقيمته معًا: for (Map.Entry<String, Integer> e : map.entrySet()) { ... }، مع قراءة e.getKey() وe.getValue(). يمكنك أيضًا المرور على map.keySet() للحصول على المفاتيح فقط، أو على map.values() للحصول على القيم فقط. لاحِظ أن HashMap لا تحافظ على ترتيب الإدخال.
ما الفرق بين get وgetOrDefault؟
تُعيد get(key) القيمة المرتبطة بمفتاح، أو null إذا كان المفتاح غير موجود، وهو ما قد يؤدي إلى NullPointerException إذا استخدمت النتيجة مباشرةً. أما getOrDefault(key, fallback) فتُعيد القيمة إن كانت موجودة، وإلا فتُعيد القيمة الافتراضية التي تمررها، فتتفادى بذلك فحص null. وهي مفيدة بوجه خاص في العدّ: counts.put(c, counts.getOrDefault(c, 0) + 1).