CodeGym /בלוג Java /Random-HE /מחיקת אלמנט מ-ArrayList
John Squirrels
רָמָה
San Francisco

מחיקת אלמנט מ-ArrayList

פורסם בקבוצה
היי! בשיעור האחרון התוודענו לכיתה ArrayList, ולמדנו כיצד לבצע את הפעולות הנפוצות ביותר בכיתה זו. בנוסף, הצבענו על מספר הבדלים בין מערך ArrayListלמערך רגיל. אבל עקפנו נושא אחד, כלומר איך למחוק אלמנטים מ- ArrayList. נדון בזה עכשיו. מחיקת אלמנט מ- ArrayList - 1כבר הזכרנו שמחיקת אלמנטים ממערך רגיל אינה נוחה במיוחד. מכיוון שאיננו יכולים למחוק את האלמנט עצמו, אנו יכולים רק "לאפס" (להגדיר ל-null) את הערך שלו:
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("Behemoth");
       cats[2] = new Cat("Lionel Messi");

       cats[1] = null;

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


@Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
פלט: [Cat{name='Thomas'}, null, Cat{name='Lionel Messi'}] אבל הגדרת רכיב מערך ל-null משאיר "חור". לא הסרנו את המיקום במערך, רק את התוכן שלו. תארו לעצמכם מה היה קורה אילו היה לנו מערך של 50 חתולים ונסיר 17 מהם בדרך זו. יהיה לנו מערך עם 17 חורים. פשוט נסה לעקוב אחריהם! זה לא מציאותי לצפות לזכור את מספר התאים הריקים שבהם אתה יכול לכתוב ערכים חדשים. אם תעשה טעות אחת, תדרוס הפניה לאובייקט שאתה רוצה. יש, כמובן, דרך לעשות זאת קצת יותר בזהירות: לאחר הסרת אלמנט, העבר את האלמנטים לקדמת המערך כדי לשים את ה"חור" בסוף:
public static void main(String[] args) {

   Cat[] cats = new Cat[4];
   cats[0] = new Cat("Thomas");
   cats[1] = new Cat("Behemoth");
   cats[2] = new Cat("Lionel Messi");
   cats[2] = new Cat("Fluffy");

   cats[1] = null;

   for (int i = 2; i < cats.length-1; i++) {
       cats [i-1] = cats [i];// Move the elements to the front of the array, so the empty position is at the end
   }

   System.out.println(Arrays.toString(cats));
}
פלט: [Cat{name='Thomas'}, Cat{name='Fluffy'}, Cat{name='Fluffy'}, null] זה נראה טוב יותר, אבל בקושי ניתן לקרוא לזה פתרון חזק. אם לא מסיבה אחרת מלבד העובדה שאנחנו צריכים לכתוב את הקוד הזה בכל פעם כשאנחנו מוחקים אלמנט ממערך! זו אפשרות גרועה. נוכל ללכת בדרך אחרת וליצור שיטה נפרדת:
public void deleteCat(Cat[] cats, int indexToDelete) {
   //...delete the cat corresponding to the index and move 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("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);
   System.out.println(cats.toString());

   cats.remove(1);

   System.out.println(cats.toString());
}
אנחנו מעבירים את האינדקס של האובייקט שלנו למתודה, שמוחקת אותו (בדיוק כמו במערך). לשיטה remove()יש שתי תכונות מיוחדות. ראשית, זה לא משאיר "חורים". זה כבר מיישם את ההיגיון הדרוש להזזת אלמנטים כאשר אלמנט מוסר מהאמצע, שכתבנו בעבר בעצמנו. תסתכל על הפלט מהקוד הקודם:
[Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}]

[Cat{name='Thomas'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}]
הוצאנו חתול אחד מהאמצע, ואת השאר הזיזו כך שלא היו חללים ריקים. שנית , הוא יכול למחוק אובייקטים לא רק לפי אינדקס (כמו מערך רגיל), אלא גם לפי הפניה :
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);
   System.out.println(cats.toString());

   cats.remove(lionel);

   System.out.println(cats.toString());
}
פלט: [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}] [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Fluffy'}] זה יכול להיות מאוד נוח אם אתה לא רוצה לעקוב תמיד אחר האינדקס של האובייקט הרצוי. נראה שמצאנו מחיקה רגילה. עכשיו בואו נדמיין את המצב הזה: אנחנו רוצים לחזור על הרשימה שלנו ולהסיר חתול עם שם ספציפי . לשם כך, נשתמש forבלולאה מהירה (נקראת גם לולאה לכל אחד), שאליה הכרנו בשיעורים של רישי:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   for (Cat cat: cats) {

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

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

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

System.out.println(cats);
חריגה בשרשור "main" java.util.ConcurrentModificationException ב-java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) ב-java.util.ArrayList$Itr.next(ArrayList.java:831) ב-Cat.main( Cat.java:25) שינינו פעולה אחת לאחרת, אך התוצאה לא השתנתה: נקבל את אותו ConcurrentModificationException . זה מתרחש בדיוק כאשר אנו מנסים לשבור את הכלל שלמעלה על ידי שינוי הרשימה תוך איטרציה עליה. ב-Java, אנו זקוקים לאובייקט מיוחד שנקרא איטרטור (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 the next element
   System.out.println(nextCat);// Display it
}
פלט: Cat{name='Thomas'} Cat{name='Behemoth'} Cat{name='Lionel Messi'} Cat{name='Fluffy'} כפי שאתה יכול לראות, החברה כבר הטמיעה שיטה מיוחדת 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 the next element
   if (nextCat.name.equals("Lionel Messi")) {
       catIterator.remove();// Delete the cat with the specified name
   }
}

System.out.println(cats);
פלט: [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Fluffy'}] אולי שמתם לב שלא ציינו לא את האינדקס או את השם בשיטת remove()האיטרטור ! האיטרטור חכם יותר ממה שהוא עשוי להיראות: remove()מסיר את האלמנט האחרון שהוחזר על ידי האיטרטור. כפי שאתה יכול לראות, זה עשה בדיוק מה שרצינו שזה יעשה :) באופן עקרוני, זה כל מה שאתה צריך לדעת על הסרת אלמנטים מ- ArrayList. ובכן, כמעט הכל. בשיעור הבא נסתכל בתוך השיעור הזה, ונראה מה קורה שם במהלך קריאות השיטה השונות :) עד אז!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION