كيف يعمل for-each في Java: دليل شامل من المبتدئ إلى المحترف

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

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

ابدأ الدورة

مقدمة: لماذا أصبحت حلقة for-each "ملك" الحلقات في Java؟

\n\n

إذا كنت قد كتبت كودًا بلغة Java لمدة أسبوع على الأقل، فبالتأكيد صادفت الصياغة for (Type var : collection). هذا التركيب المختصر يُسمى حلقة for-each (أو الحلقة المحسّنة enhanced for loop). ظهرت لأول مرة في Java 5 (عام 2004) وسرعان ما أصبحت المفضلة لدى المطورين.

\n\n

ولكن هل تعرف ما يحدث تحت الغطاء عندما تواجه JVM هذه الصياغة؟ لماذا قد تكون حلقة for-each أبطأ من الحلقة العادية for مع المصفوفات؟ ولماذا ترمي أحيانًا استثناء ConcurrentModificationException حتى لو لم تقم بحذف أي شيء؟

\n\n

في هذا المقال، سنشرح كيف تعمل حلقة for-each في Java على مستوى البايت كود، ونقارنها بالحلقات التقليدية، ونتعلم كيفية تجنب الأخطاء الشائعة. هيا بنا!

\n\n

الصياغة والتعريف الأولي

\n\n

الصياغة الأساسية هي:

\n\n
for (نوع_العنصر متغير : مجموعة_أو_مصفوفة) {\n    // جسم الحلقة\n}
\n\n

مثال مع ArrayList:

\n\n
List<String> names = Arrays.asList("أحمد", "سارة", "خالد");\n\nfor (String name : names) {\n    System.out.println("مرحبًا، " + name + "!");\n}
\n\n

النتيجة:

\n\n
مرحبًا، أحمد!\nمرحبًا، سارة!\nمرحبًا، خالد!
\n\n

الأمر بسيط، أليس كذلك؟ لكن خلف هذه البساطة يختبئ آليتان مختلفتان تمامًا — واحدة للمصفوفات وأخرى للمجموعات.

\n\n

كيف تعمل حلقة for-each مع المصفوفات؟

\n\n

عند كتابة حلقة for-each لمصفوفة، يقوم مترجم Java بتحويلها إلى حلقة for عادية بمؤشر (index). لا يوجد أي سحر.

\n\n

الكود الخاص بك:

\n\n
int[] numbers = {10, 20, 30};\n\nfor (int num : numbers) {\n    System.out.println(num);\n}
\n\n

بعد الترجمة (قم بفك ترجمة البايت كود — لتتأكد):

\n\n
int[] numbers = new int[]{10, 20, 30};\nfor (int i = 0; i < numbers.length; i++) {\n    int num = numbers[i];\n    System.out.println(num);\n}
\n\n

استنتاج مهم: بالنسبة للمصفوفات، حلقة for-each والحلقة العادية for متطابقتان في الأداء. لا توجد تكاليف إضافية للمحولات (iterators).

\n\n

كيف تعمل حلقة for-each مع المجموعات (List, Set)؟

\n\n

هنا يبدأ الجزء المثير للاهتمام. بالنسبة للمجموعات، تستخدم حلقة for-each المحول (iterator).

\n\n

الكود الخاص بك:

\n\n
List<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (String lang : list) {\n    System.out.println(lang);\n}
\n\n

يقوم المترجم بتحويل هذا إلى:

\n\n
List<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (Iterator<String> it = list.iterator(); it.hasNext(); ) {\n    String lang = it.next();\n    System.out.println(lang);\n}
\n\n

أي:

\n
    \n
  • يتم استدعاء list.iterator() — نحصل على المحول.
  • \n
  • في الشرط — it.hasNext().
  • \n
  • في الجسم — it.next().
  • \n
  • يتم تهيئة المتغير lang بنتيجة next().
  • \n
\n\n

ماذا يعني هذا عمليًا؟

\n
    \n
  • لا يمكن حذف العناصر داخل حلقة for-each باستخدام list.remove() — ستحصل على ConcurrentModificationException. استخدم it.remove() أو الحلقة العادية for.
  • \n
  • لا يمكن تعديل المجموعة (إضافة/حذف) أثناء التكرار — نفس الخطأ.
  • \n
  • الأداء: بالنسبة لـ ArrayList، الفرق مع الحلقة العادية for ضئيل (إنشاء كائن المحول). بالنسبة لـ LinkedList — حلقة for-each أسرع بكثير من for (int i = 0; i < list.size(); i++)، لأن get(i) في LinkedList هو O(n) في كل مرة!
  • \n
\n\n

ماذا عن Map؟ التكرار عبر أزواج المفتاح-القيمة

\n\n

لا تطبق Map واجهة Iterable بشكل مباشر، لذا فإن حلقة for-each

المدونات

توصيات الكتب