HashMap في Java: 5 أخطاء قاتلة عند التكرار وكيفية تجنبها

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: HashMap — صديق أم عدو؟



كل مطور Java يواجه عاجلاً أم آجلاً HashMap. إنها، على الأرجح، أشهر تطبيق لواجهة Map في Java. لكن المفارقة: الجميع يعرف كيفية إضافة عنصر إلى الخريطة، ولكن عندما يتعلق الأمر بتكرارها (Iteration)، تبدأ الفوضى الحقيقية. سؤال "Iterate through a HashMap" على Stack Overflow حصد آلاف المشاهدات، وعدد الأخطاء في المشاريع الحقيقية يفوق الحد.



في هذه المقالة، سنحلل أخطر 5 أخطاء عند تكرار HashMap في Java، والتي قد تؤدي إلى تعطل التطبيق، فقدان البيانات، وكود غير قابل للقراءة. ستتعلم ليس فقط كيف تفعل ذلك بشكل خاطئ، بل ستحصل أيضاً على قوالب عمل جاهزة لكل حالة. هيا بنا!



الخطأ رقم 1: ConcurrentModificationException — "الحذف أثناء السير"



ما المشكلة؟

الخطأ الأكثر كلاسيكية. تقوم بالتكرار عبر HashMap وتحاول حذف عنصر باستخدام map.remove(key). في 90% من الحالات، يؤدي هذا إلى ConcurrentModificationException.



// ❌ خطير: سيؤدي إلى استثناء!HashMap<String, Integer> scores = new HashMap<>();scores.put("Alice", 90);scores.put("Bob", 50);scores.put("Charlie", 70);

for (String name : scores.keySet()) { if (scores.get(name) < 60) { scores.remove(name); // ConcurrentModificationException! }}


لماذا يحدث هذا؟

المكرر الداخلي لـ HashMap يحتفظ بعداد للتعديلات (modCount). عندما تحذف عنصراً مباشرةً من المجموعة، يتغير العداد، لكن المكرر لا يعلم بذلك. في التكرار التالي، يتحقق: "مرحباً، العداد غير متطابق! نرمي استثناء".



كيف نصلحه؟

استخدم Iterator مع طريقته remove():



// ✅ صحيح: استخدام IteratorHashMap<String, Integer> scores = new HashMap<>();scores.put("Alice", 90);scores.put("Bob", 50);scores.put("Charlie", 70);

Iterator<Map.Entry<String, Integer>> iterator = scores.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() < 60) { iterator.remove(); // حذف آمن! }}


مع Java 8 ظهرت طريقة أكثر أناقة — removeIf():



// ✅ أفضل: removeIf (Java 8+)scores.entrySet().removeIf(entry -> entry.getValue() < 60);


الخطأ رقم 2: التكرار البطيء عبر keySet() و get()



ما المشكلة؟

كثير من المبتدئين يفعلون هكذا:



// ❌ بطيء: keySet() + get() — بحث مزدوجfor (String key : map.keySet()) {    System.out.println("Key: " + key + ", Value: " + map.get(key));}


للوهلة الأولى، كل شيء يعمل. لكن المشكلة أن map.get(key) داخلياً يبحث مرة أخرى عن التجزئة (hash) ويمر عبر الدلو (bucket). في كل دورة من الحلقة، تقوم باستدعاءين لـ HashMap بدلاً من استدعاء واحد. بالنسبة للخرائط الصغيرة، هذا غير ملحوظ، لكن مع مئات الآلاف من السجلات، يؤدي ذلك إلى فقدان في الأداء يصل إلى 30-40%.



كيف نصلحه؟

استخدم دائماً entrySet() — فهو يعيد أزواج "مفتاح-قيمة" في تمريرة واحدة:



// ✅ سريع: entrySet() — تمريرة واحدةfor (Map.Entry<String, Integer> entry : map.entrySet()) {    System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());}


أو استخدم forEach مع Java 8:



// ✅ موجز: forEach (Java 8+)map.forEach((key, value) -> {    System.out.println("Key: " + key + ", Value: " + value);});


الخطأ رقم 3: الخلط في ترتيب العناصر



ما المشكلة؟

لقد كتبت كوداً يتكرر عبر HashMap، وتتوقع أن تكون العناصر بترتيب الإضافة. لكنك تحصل على تسلسل مختلف تماماً. هذا يؤدي إلى أخطاء برمجية (bugs) يصعب تتبعها.

المدونات

توصيات الكتب