التحكّم في التدفّق والحلقات

حلقة for-each المحسّنة

15 دقيقة الدرس 7 من 14

حلقة for-each المحسّنة

حلقة for التقليدية قوية، لكنها تحمل كودًا مكررًا: تُعلن عن عداد، وتقارنه بالطول، وتزيده — فقط لتزور كل عنصر. تقدم Java بديلًا أنظف يُسمى حلقة for المحسّنة (وتُعرف أيضًا بـ حلقة for-each). صُمِّمت لوظيفة واحدة تحديدًا: التكرار على كل عنصر في مصفوفة أو مجموعة، دون إدارة فهرس على الإطلاق.

البنية العامة

البنية مقصودةً في بساطتها:

for (Type element : collectionOrArray) { // استخدم element هنا }

اقرأ النقطتين : بمعنى "في". فعبارة for (String name : names) تُقرأ: "لكل String اسمه name في names". تتكفّل Java بجلب كل عنصر والتوقف في النهاية — لا تفعل شيئًا إضافيًا.

التكرار على مصفوفة

إليك المهمة ذاتها مكتوبةً أولًا بحلقة for التقليدية ثم بحلقة for-each، لترى الفرق:

String[] fruits = {"Apple", "Banana", "Cherry"}; // حلقة for التقليدية for (int i = 0; i < fruits.length; i++) { System.out.println(fruits[i]); } // حلقة for-each المحسّنة — نفس الناتج، بلا ضجيج for (String fruit : fruits) { System.out.println(fruit); }

كلتاهما تطبع:

Apple Banana Cherry

نسخة for-each لا تحتوي على متغير فهرس، ولا مقارنة طول، ولا [i]. يقول الكود ما يعنيه تمامًا: "لكل فاكهة، اطبعها".

التكرار على قائمة List

تعمل for-each مع أي كلاس ينفّذ واجهة Iterable، وهذا يشمل جميع مجموعات Java القياسية مثل ArrayList وLinkedList وHashSet:

import java.util.List; List<Integer> scores = List.of(85, 92, 78, 95, 60); int total = 0; for (int score : scores) { total += score; } System.out.println("Total: " + total); System.out.println("Average: " + (total / scores.size()));
تُنشئ List.of() قائمةً غير قابلة للتعديل. إنها طريقة حديثة وموجزة لإنشاء قائمة للقراءة فقط من مجموعة قيم. ستصادفها كثيرًا في الأمثلة طوال هذه الدورة.

التكرار على مجموعة Set

تعمل for-each بالطريقة ذاتها مع Set. الفرق أن Set لا يضمن ترتيبًا معينًا، فقد تظهر العناصر بأي تسلسل:

import java.util.Set; Set<String> colors = Set.of("Red", "Green", "Blue"); for (String color : colors) { System.out.println(color); // الترتيب غير مضمون }

متى لا تكون for-each مناسبة

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

  1. تحتاج إلى الفهرس. لا تمنحك for-each أي معلومة عن الموضع. إذا أردت طباعة "العنصر 0 هو Apple"، فأنت بحاجة إلى i.
  2. تحتاج إلى تعديل عنصر مصفوفة. تعيين قيمة لمتغير الحلقة لا يغيّر المصفوفة الأصلية:
int[] numbers = {1, 2, 3}; for (int n : numbers) { n = n * 2; // هذا يغيّر النسخة المحلية من n فقط } // numbers لا تزال {1, 2, 3} — لم يتغير شيء! System.out.println(numbers[0]); // يطبع 1
لا تستطيع for-each تعديل عناصر المصفوفة. متغير الحلقة هو نسخة من كل قيمة (للأنواع الأولية) أو نسخة من المرجع (للكائنات). إعادة تعيين المتغير لا تؤثر على المصفوفة أو القائمة الأصلية. استخدم حلقة for التقليدية بفهرس إذا احتجت تحديث العناصر في مكانها.
  1. تحتاج إلى التكرار على مصفوفتين في وقت واحد. تعمل for-each على مجموعة واحدة في كل مرة. إذا أردت مقارنة مصفوفتين أو دمجهما عنصرًا بعنصر، فأنت بحاجة إلى فهرس.
  2. تحتاج إلى التكرار بالعكس. تسير for-each دائمًا للأمام من أول عنصر إلى آخره. لا يوجد وضع عكسي.
  3. تحتاج إلى حذف عناصر من مجموعة أثناء التكرار. الحذف من List داخل for-each يُطلق ConcurrentModificationException. استخدم Iterator أو removeIf() بدلًا من ذلك.
قاعدة عامة: الجأ إلى for-each كلما أردت فقط قراءة كل عنصر بالترتيب. انتقل إلى حلقة for التقليدية حالما تحتاج إلى فهرس، أو تكرار عكسي، أو تعديل في مكانه.

for-Each مع المصفوفات متعددة الأبعاد

يمكنك تداخل حلقتي for-each لاجتياز مصفوفة ثنائية الأبعاد. الحلقة الخارجية تمنحك كل صف (وهو مصفوفة بحد ذاته)، والحلقة الداخلية تمنحك كل عنصر في ذلك الصف:

int[][] grid = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; for (int[] row : grid) { for (int value : row) { System.out.print(value + " "); } System.out.println(); }

الناتج:

1 2 3 4 5 6 7 8 9

الخلاصة

تُزيل حلقة for-each المحسّنة فوضى إدارة الفهارس وتجعل كود التكرار أسهل قراءة. استخدمها بحرية عندما تريد زيارة كل عنصر في مصفوفة أو مجموعة بالترتيب. تذكّر قيودها — لا فهرس، ولا تعديل مباشر لعناصر المصفوفة، ولا تكرار عكسي — وانتقل إلى حلقة for التقليدية متى انطبق أيٌّ من تلك القيود.