הימנעות מה- ConcurrentModificationException בג'אווה

1. הקדמה

במאמר זה, נסתכל על ה- ConcurrentModificationException מעמד.

ראשית, נסביר כיצד זה עובד, ולאחר מכן נוכיח זאת באמצעות מבחן להפעלתו.

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

2. טריגר א ConcurrentModificationException

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

@Test (צפוי = ConcurrentModificationException.class) חלל ציבורי תוך RemovingDuringIteration_shouldThrowException () זורק מופרע Exception {רשימה שלמה = newArrayList (1, 2, 3); עבור (מספר שלם: מספרים שלמים) {integers.remove (1); }}

כפי שאנו רואים, לפני סיום החזרה שלנו אנו מסירים אלמנט. זה מה שמפעיל את החריג.

3. פתרונות

לפעמים, אולי נרצה להסיר אלמנטים מאוסף תוך כדי איטרציה. אם זה המקרה, יש כמה פתרונות.

3.1. שימוש ישיר באינטרטור

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

עבור (איטרטור איטרטור = מספרים שלמים.איטרטור (); איטרטור.האסנאסט ();) {מספר שלם שלם = איטרטור.נאקסט (); אם (מספר שלם == 2) {iterator.remove (); }}

כעת נבחין כי אין יוצא מן הכלל. הסיבה לכך היא שה- לְהַסִיר() השיטה אינה גורמת ל- a ConcurrentModificationException. זה בטוח להתקשר תוך כדי איטרציה.

3.2. לא מסיר במהלך איטרציה

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

רשימת מספרים שלמים = newArrayList (1, 2, 3); List toRemove = newArrayList (); עבור (מספר שלם: מספרים שלמים) {אם (מספר שלם == 2) {toRemove.add (מספר שלם); }} מספרים שלמים.הסר את כל (toRemove); assertThat (מספרים שלמים) .contains בדיוק (1, 3); 

זוהי דרך יעילה נוספת לעקוף את הבעיה.

3.3. באמצעות removeIf ()

ג'אווה 8 הציגה את removeIf () שיטה ל אוסף מִמְשָׁק. המשמעות היא שאם אנו עובדים עם זה, נוכל להשתמש ברעיונות של תכנות פונקציונלי כדי להשיג שוב את אותן התוצאות:

רשימת מספרים שלמים = newArrayList (1, 2, 3); integers.removeIf (i -> i == 2); assertThat (מספרים שלמים) .contains בדיוק (1, 3);

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

3.4. סינון באמצעות זרמים

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

מספרים שלמים של אוסף = newArrayList (1, 2, 3); רשימה שנאספה = מספרים שלמים .stream () .filter (i -> i! = 2) .map (Object :: toString) .collect (toList ()); assertThat (שנאסף). מכיל בדיוק ("1", "3");

עשינו את ההפך מהדוגמה הקודמת שלנו, על ידי מתן פרדיקט לקביעת אלמנטים שייכללו, לא יכללו. היתרון הוא שנוכל לשלב פונקציות אחרות לצד ההסרה. בדוגמה אנו משתמשים בפונקציונלי מַפָּה(), אך נוכל להשתמש בפעולות רבות יותר אם נרצה בכך.

4. מסקנה

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

ניתן למצוא את היישום של דוגמאות אלה ב- GitHub. זהו פרויקט של Maven, ולכן צריך להיות קל להפעלה כפי שהוא.


$config[zx-auto] not found$config[zx-overlay] not found