Menu

לולאת for-each ב-Java: תחביר, דוגמאות ומלכודות

לולאת for-each (ה-for המשופר) של Java בהסבר ברור: מעבר נקי על מערכים ואוספים, מתי להשתמש בה, ומלכודת השינוי שמכשילה את כולם.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

כשהאינדקס רק מפריע

לולאת for עם מונה נותנת לכם מונה, תנאי וצעד עדכון. אבל בהרבה מאוד מקרים לא באמת אכפת לכם מהמיקום של איבר: אתם רק רוצים לעשות משהו עם כל אחד מהם, לפי הסדר, מההתחלה ועד הסוף. ניהול אינדקס בשביל זה הוא עבודה מיותרת, ובדיוק שם מתגנבים באגים של טעות באחד.

לולאת for-each (השם של Java עבורה הוא ה-for המשופר) מוותרת על המונה לגמרי. נותנים שם למשתנה, מכוונים אותו לאוסף, והלולאה מגישה לכם כל איבר בתורו.

תחביר בסיסי

הצורה היא for (Type element : collection). קראו את הנקודתיים כמילה "ב-":

אין i, אין scores.length, אין scores[i]. בכל סיבוב, score הוא האיבר הבא. הלולאה רצה פעם אחת לכל איבר ונעצרת אוטומטית כשאין עוד איברים: אי אפשר לחרוג מהסוף או להתחיל איבר אחד מוקדם מדי.

מעבר על List

אותה לולאה עובדת על כל דבר שאפשר לעבור עליו, כולל List, Set ושאר סוגי האוספים. טיפוס האיבר בא לפני שם המשתנה:

שימו לב שלא הייתם צריכים לדעת או להתעניין אם langs מבוסס על מערך, על רשימה מקושרת או על משהו אחר: for-each עובדת אותו דבר על כולם. זה הכוח האמיתי שלה: תחביר קריא אחד לכל אוסף.

var חוסך את שם הטיפוס

אם טיפוס האיבר ארוך או ברור מאליו, var נותן לקומפיילר להסיק אותו כדי שלא תחזרו על עצמכם:

בלי var, משתנה הלולאה היה הביטוי הארוך Map.Entry<String, Integer>. var שומר על קריאות, והטיפוס עדיין נבדק במלואו בזמן הקומפילציה: זה לא טיפוס דינמי ורופף.

מלכודת השינוי

הנה הכלל שתופס את כולם: אי אפשר להוסיף לאוסף או להסיר ממנו בזמן שלולאת for-each עוברת עליו. ניסיון כזה זורק ConcurrentModificationException:

List<String> items = new ArrayList<>(List.of("a", "b", "c"));

for (String item : items) {
    if (item.equals("b")) {
        items.remove(item);   // זורק ConcurrentModificationException
    }
}

הלולאה מבחינה שהרשימה השתנתה מתחתיה ומפסיקה, במקום לדלג על איברים או לחזור עליהם בשקט. כדי להסיר בבטחה, רדו רמה ל-Iterator מפורש, שיש לו remove() שהלולאה יודעת עליו:

קיצור דרך נפוץ הוא items.removeIf(item -> item.equals("b")), שמשתמש בביטוי lambda כדי לעשות את אותו הדבר בשורה אחת.

קריאה בלבד, בלי השמה מחדש

מגבלה עדינה נוספת: השמה למשתנה הלולאה משנה רק את העותק המקומי, לא את האוסף. זה מפתיע אנשים שמגיעים משפות שבהן משתנה הלולאה הוא הפניה חיה:

אם צריך לכתוב חזרה למערך, צריך את האינדקס, כלומר את לולאת ה-for הקלאסית עם מונה: for (int i = 0; i < nums.length; i++) nums[i] = nums[i] * 10;. עבור איברים שהם אובייקטים, אפשר לשנות את האובייקט שהמשתנה מצביע עליו (למשל לקרוא ל-setter), אבל אי אפשר להחליף אותו באוסף.

break ו-continue עדיין עובדים

for-each היא לולאה אמיתית, ולכן break ו-continue מתנהגים בדיוק כמו בכל מקום אחר: break יוצא מהלולאה, continue מדלג לאיבר הבא:

זה מדפיס keep ואז keep: הקוד מדלג על "skip" ועוצר ב-"stop" לפני שהוא מגיע ל-"never". אז אתם לא חייבים לבקר בכל איבר; אתם רק מוותרים על האינדקס בתמורה לקוד נקי יותר.

הבא בתור: מערכים

עברתם כבר כמה פעמים על מערכים בלולאה בלי להתעכב על מה הם באמת: מכלים בגודל קבוע עם אינדקסים ועם השדה .length. העמוד הבא חוזר להתחלה ומסביר מערכים כמו שצריך: איך מצהירים עליהם, עם אילו ערכי ברירת מחדל הם מתחילים, ואיך הגודל הקבוע שלהם שונה מ-ArrayList שיכולה לגדול.

שאלות נפוצות

מה זו לולאת for-each ב-Java?

לולאת for-each (שנקראת גם ה-for המשופר) עוברת על כל איבר במערך או באוסף בלי מונה: for (Type item : collection) { ... }. קראו אותה כ-"לכל item ב-collection". היא נקייה יותר מלולאת for עם מונה כשצריך רק כל איבר ואף פעם לא את האינדקס.

מה ההבדל בין לולאת for ללולאת for-each ב-Java?

לולאת ה-for הקלאסית משתמשת במונה מפורש (for (int i = 0; i < arr.length; i++)), כך שאתם שולטים באינדקס ובכיוון. לולאת for-each, for (Type x : arr), היא בלי אינדקס: היא מבקרת בכל איבר לפי הסדר. השתמשו ב-for-each למעבר קריאה בלבד מההתחלה לסוף; השתמשו בלולאה עם מונה כשצריך את האינדקס, רוצים לדלג על איברים או לשנות את המבנה של האוסף.

למה לולאת ה-for-each שלי זורקת ConcurrentModificationException?

כי קראתם ל-add() או ל-remove() על האוסף בזמן שעברתם עליו עם for-each. הלולאה מזהה את השינוי המבני וזורקת חריגה כדי להגן עליכם מהתנהגות לא מוגדרת. כדי להסיר איברים בבטחה, השתמשו ב-Iterator מפורש ובמתודה remove() שלו, או אספו את הפריטים למחיקה והסירו אותם אחרי הלולאה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל