JavaRush /مدونة جافا /Random-AR /إزالة عنصر من 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، لإزالة العناصر أثناء التكرار، تحتاج إلى استخدام كائن خاص - مكرر (فئة 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