שלום! בהרצאה האחרונה התוודענו לשיעור ArrayList , וגם למדנו איך לבצע איתה את הפעולות הנפוצות ביותר. בנוסף, הדגשנו לא מעט הבדלים בין ArrayList למערך רגיל. עכשיו בואו נסתכל על הסרת אלמנט מה-ArrayList. כבר אמרנו שמחיקת אלמנטים במערך רגיל אינה נוחה במיוחד. מכיוון שאיננו יכולים למחוק את התא עצמו, אנו יכולים רק "לאפס" את הערך שלו:
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
. ליתר דיוק - כמעט הכל. בהרצאה הבאה נבחן את ה"פנים" של השיעור הזה ונראה מה קורה שם במהלך המבצעים :) נתראה!
GO TO FULL VERSION