JavaRush /בלוג Java /Random-HE /הסרת אלמנט מ-ArrayList ב-Java

הסרת אלמנט מ-ArrayList ב-Java

פורסם בקבוצה
שלום! בהרצאה האחרונה התוודענו לשיעור ArrayList , וגם למדנו איך לבצע איתה את הפעולות הנפוצות ביותר. בנוסף, הדגשנו לא מעט הבדלים בין ArrayList למערך רגיל. עכשיו בואו נסתכל על הסרת אלמנט מה-ArrayList. כבר אמרנו שמחיקת אלמנטים במערך רגיל אינה נוחה במיוחד. הסרת רכיב מ-ArrayList - 1מכיוון שאיננו יכולים למחוק את התא עצמו, אנו יכולים רק "לאפס" את הערך שלו:
public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat[] cats = new Cat[3];
       cats[0] = new Cat("Thomas");
       cats[1] = new Cat("Hippopotamus");
       cats[2] = new Cat("Philip Markovich");

       cats[1] = null;

       System.out.println(Arrays.toString(cats));
   }


@Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
סיכום:

[Cat{name='Томас'}, null, Cat{name='Фorпп Маркович'}]
אבל בעת איפוס, "חור" נשאר במערך. אנחנו לא מוחקים את התא, אלא רק את התוכן שלו. תארו לעצמכם מה יקרה אם יהיה לנו מערך של 50 חתולים, 17 מהם מחקנו בצורה זו. יהיה לנו מערך עם 17 חורים, ונשמור עליהם! לזכור בעל פה את מספר התאים הריקים שבהם אתה יכול לכתוב ערכים חדשים זה לא ריאלי. תטעו פעם אחת ותדרוס את התא עם ההתייחסות הרצויה לאובייקט. יש, כמובן, הזדמנות לעשות את זה קצת יותר בזהירות: לאחר המחיקה, העבר את רכיבי המערך להתחלה, כך שה"חור" יהיה בסוף:
public static void main(String[] args) {

   Cat[] cats = new Cat[4];
   cats[0] = new Cat("Thomas");
   cats[1] = new Cat("Hippopotamus");
   cats[2] = new Cat("Philip Markovich");
   cats[3] = new Cat("Fluff");

   cats[1] = null;

   for (int i = 2; i < cats.length-1; i++) {
       //move the elements to the beginning so that the empty cell is at the end
       cats[i-1] = cats[i];
       cats[i] = null;
   }

   System.out.println(Arrays.toString(cats));
}
סיכום:

[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}, null]
כעת נראה שזה נראה טוב יותר, אבל בקושי אפשר לקרוא לזה פתרון יציב. לכל הפחות, כי נצטרך לכתוב את הקוד הזה ביד בכל פעם שנסיר אלמנט מהמערך! אפשרות גרועה. אתה יכול ללכת בדרך אחרת וליצור שיטה נפרדת:
public void deleteCat(Cat[] cats, int indexToDelete) {
   //...remove the cat by index and shift the elements
}
אבל זה גם מועיל מעט: שיטה זו יכולה לעבוד רק עם אובייקטים Cat, אבל לא יכולה לעבוד עם אחרים. כלומר, אם יש עוד 100 מחלקות בתוכנית איתם נרצה להשתמש במערכים, נצטרך לכתוב את אותה שיטה עם אותו היגיון בדיוק בכל אחד מהם. זהו כישלון מוחלט -_- אבל במחלקת ArrayList הבעיה הזו נפתרה בהצלחה! הוא מיישם שיטה מיוחדת להסרת אלמנטים - remove():
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);
   System.out.println(cats.toString());

   cats.remove(1);

   System.out.println(cats.toString());
}
העברנו את האינדקס של האובייקט שלנו לשיטה, והוא נמחק (ממש כמו במערך). לשיטה remove()יש שתי תכונות. ראשית , זה לא משאיר "חורים". זה כבר מיישם את ההיגיון של העברת אלמנטים בעת הסרת אלמנט מהאמצע, שכתבנו בעבר ביד. תסתכל על הפלט של הקוד הקודם בקונסולה:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
הוצאנו חתול אחד מהאמצע ואת האחרים הזיזו כך שלא היו רווחים. שנית , הוא יכול למחוק אובייקט לא רק על ידי אינדקס (כמו מערך רגיל), אלא גם על ידי התייחסות לאובייקט :
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);
   System.out.println(cats.toString());

   cats.remove(philipp);

   System.out.println(cats.toString());
}
סיכום:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
זה יכול להיות מאוד נוח אם אתה לא רוצה לשמור תמיד את האינדקס של האובייקט הרצוי בראש שלך. נראה שסיימנו את המחיקה הרגילה. עכשיו בואו נדמיין את המצב הזה: אנחנו רוצים לעבור על רשימת האלמנטים שלנו ולהסיר חתול עם שם מסוים. לשם כך אנו משתמשים באופרטור לולאה מיוחד for- for each. אתה יכול ללמוד עוד על זה בהרצאה זו .
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   for (Cat cat: cats) {

       if (cat.name.equals("Hippopotamus")) {
           cats.remove(cat);
       }
   }

   System.out.println(cats);
}
הקוד נראה די הגיוני. עם זאת, התוצאה עשויה להפתיע אותך:

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
  at java.util.ArrayList$Itr.next(ArrayList.java:831)
  at Cat.main(Cat.java:25)
סוג של שגיאה, ולא ברור למה היא הופיעה פתאום. ישנם מספר ניואנסים בתהליך זה שצריך להתמודד איתם. כלל כללי שאתה צריך לזכור: אתה לא יכול לחזור על אוסף ולשנות את האלמנטים שלו בו-זמנית. כן, כן, בדיוק שינוי, ולא רק מחיקה. אם תנסה בקוד שלנו להחליף את הרחקת חתולים בהכנסת חתולים חדשים, התוצאה תהיה זהה:
for (Cat cat: cats) {

   cats.add(new Cat("Salem Saberhegen"));
}

System.out.println(cats);

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
  at java.util.ArrayList$Itr.next(ArrayList.java:831)
  at Cat.main(Cat.java:25)
שינינו פעולה אחת לאחרת, אך התוצאה לא השתנתה: אותה שגיאה ConcurrentModificationException. זה מתרחש בדיוק כאשר אנו מנסים לשבור כלל ולשנות את הרשימה תוך כדי איטרציה דרכו. ב-Java, כדי להסיר אלמנטים במהלך איטרציה, אתה צריך להשתמש באובייקט מיוחד - איטרטור (class Iterator). הכיתה Iteratorאחראית לעבור בבטחה ברשימת האלמנטים. זה די פשוט כי יש לו רק 3 שיטות:
  • hasNext()- מחזיר trueאו falseתלוי אם יש אלמנט הבא ברשימה, או אם כבר הגענו לאחד האחרון.
  • next()- מחזיר את הרכיב הבא ברשימה
  • remove()- מסיר רכיב מהרשימה
כפי שאתה יכול לראות, האיטרטור ממש "מותאם" לצרכים שלנו, ואין בזה שום דבר מסובך. לדוגמה, אנו רוצים לבדוק אם הרשימה שלנו מכילה את האלמנט הבא, ואם כן, להדפיס אותו לקונסולה:
Iterator<Cat> catIterator = cats.iterator();//create an iterator
while(catIterator.hasNext()) {//as long as there are elements in the list

   Cat nextCat = catIterator.next();//get next element
   System.out.println(nextCat);// print it to the console
}
סיכום:

Cat{name='Томас'}
Cat{name='Бегемот'}
Cat{name='Фorпп Маркович'}
Cat{name='Пушок'}
כפי שאתה יכול לראות, המחלקה ArrayListכבר מיישמת שיטה מיוחדת ליצירת איטרטור - iterator(). כמו כן, שימו לב שכאשר יוצרים איטרטור, אנו מציינים את מחלקת האובייקטים שאיתם הוא יעבוד ( <Cat>). בסופו של דבר, אנו יכולים לפתור בקלות את הבעיה המקורית שלנו באמצעות איטרטור. לדוגמה, בואו נמחק חתול בשם "פיליפ מרקוביץ'":
Iterator<Cat> catIterator = cats.iterator();//create an iterator
while(catIterator.hasNext()) {//as long as there are elements in the list

   Cat nextCat = catIterator.next();//get next element
   if (nextCat.name.equals("Philip Markovich")) {
       catIterator.remove();//delete the cat with the desired name
   }
}

System.out.println(cats);
סיכום:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
אולי שמתם לב שלא ציינו לא את אינדקס האלמנטים או את שם משתנה ההתייחסות בשיטת האיטרטור remove()! האיטרטור חכם יותר ממה שזה נראה: השיטה remove()מסירה את האלמנט האחרון שהוחזר על ידי האיטרטור. כפי שאתה יכול לראות, זה עבד בדיוק לפי הצורך :) זה בעצם כל מה שאתה צריך לדעת על הסרת אלמנטים מ ArrayList. ליתר דיוק - כמעט הכל. בהרצאה הבאה נבחן את ה"פנים" של השיעור הזה ונראה מה קורה שם במהלך המבצעים :) נתראה!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION