JavaRush /Java блог /Random UA /Видалення елемента зі списку ArrayList до Java

Видалення елемента зі списку ArrayList до Java

Стаття з групи Random UA
Вітання! Минулої лекції ми познайомабося з класом 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("Томас");
       cats[1] = new Cat("Бегемот");
       cats[2] = new Cat("Філіп Маркович");

       cats[1] = null;

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


@Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
Висновок:

[Cat{name='Томас'}, null, Cat{name='Фабопп Маркович'}]
Але при обнуленні в масиві залишається "дірка". Адже ми видаляємо не комірку, а тільки її вміст. Уяви, що буде, якщо у нас масив з 50 котів, 17 з яких ми видалабо таким способом. У нас буде масив з 17-ма дірками, і йди слідкуй за ними! Пам'ятати пам'ять номера порожніх осередків, куди можна записувати нові значення - неможливо. Один раз помабошся — і перезапишеш осередок із потрібним посиланням на об'єкт. Є, звичайно, можливість зробити трохи акуратніше: після видалення зрушити елементи масиву до початку, так, щоб "дірка" виявилася наприкінці:
public static void main(String[] args) {

   Cat[] cats = new Cat[4];
   cats[0] = new Cat("Томас");
   cats[1] = new Cat("Бегемот");
   cats[2] = new Cat("Філіп Маркович");
   cats[3] = new Cat("Пушок");

   cats[1] = null;

   for (int i = 2; i < cats.length-1; i++) {
       //переміщаємо елементи до початку, щоб порожній осередок опинився в кінці
       cats[i-1] = cats[i];
       cats[i] = null;
   }

   System.out.println(Arrays.toString(cats));
}
Висновок:

[Cat{name='Томас'}, Cat{name='Фабопп Маркович'}, Cat{name='Пушок'}, null]
Тепер ніби виглядає краще, але це навряд чи можна назвати стабільним рішенням. Як мінімум тому, що нам доведеться щоразу писати цей код руками, коли ми видалятимемо елемент з масиву! Поганий варіант. Можна було б піти іншим шляхом і створити окремий метод:
public void deleteCat(Cat[] cats, int indexToDelete) {
   //... видаляємо кота за індексом і зрушуємо елементи
}
Але від цього користі теж мало: цей метод вміє працювати тільки з об'єктами Cat, а з іншими не вміє. Тобто якщо в програмі буде ще 100 класів, з якими ми захочемо використовувати масиви, нам доведеться в кожному з них писати такий же метод із такою самою логікою. Це взагалі провал -_- Але в класі ArrayList цю проблему успішно вирішено! У ньому реалізовано спеціальний метод для видалення елементів remove().
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Філіп Маркович");
   Cat pushok = new Cat("Пушок");

   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='Фабопп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Фабопп Маркович'}, Cat{name='Пушок'}]
Ми видалабо з середини одного кота, і інші були пересунуті так, щоб не залишалося прогалин. По-друге , він може видаляти об'єкт не тільки за індексом (як звичайний масив), але і за посиланням на об'єкт :
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Філіп Маркович");
   Cat pushok = new Cat("Пушок");

   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='Фабопп Маркович'}, 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("Томас");
   Cat behemoth = new Cat("Бегемот");
   Cat philipp = new Cat("Філіп Маркович");
   Cat pushok = new Cat("Пушок");

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

   for (Cat cat: cats) {

       if (cat.name.equals("Бегемот")) {
           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("Сейлем Сейберхеген"));
}

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();//Створюємо ітератор
while(catIterator.hasNext()) {//До тих пір, поки в списку є елементи

   Cat nextCat = catIterator.next();//отримуємо наступний елемент
   System.out.println(nextCat);//виводимо його в консоль
}
Висновок:

Cat{name='Томас'}
Cat{name='Бегемот'}
Cat{name='Фабопп Маркович'}
Cat{name='Пушок'}
Як бачиш, у класі ArrayListвже реалізовано спеціальний метод для створення ітератора iterator(). Крім того, зверніть увагу, що при створенні ітератора ми вказуємо клас об'єктів, з якими він повинен працювати ( <Cat>). Зрештою, ми легко вирішуємо наше споконвічне завдання за допомогою ітератора. Наприклад, видалимо кота з ім'ям "Філіп Маркович":
Iterator<Cat> catIterator = cats.iterator();//Створюємо ітератор
while(catIterator.hasNext()) {//До тих пір, поки в списку є елементи

   Cat nextCat = catIterator.next();//отримуємо наступний елемент
   if (nextCat.name.equals("Філіп Маркович")) {
       catIterator.remove();//видаляємо кота з потрібним ім'ям
   }
}

System.out.println(cats);
Висновок:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
Можливо, ти помітив, що ми не вказували ні індекс елемента, ні ім'я змінної-посилання в методі ітератора remove()! Ітератор розумніший, ніж може здатися: метод remove()видаляє останній елемент, який повернуто ітератором. Як бачиш, він спрацював саме так, як потрібно :) Ось в принципі все, що тобі потрібно знати про видалення елементів з ArrayList. Точніше – майже все. У наступній лекції ми заглянемо у “нутрощі” цього класу, і подивимося, що там відбувається під час здійснення операцій :) До зустрічі!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ