JavaRush /Java блог /Java Developer /Удаление элемента из списка ArrayList в Java
Автор
Владимир Портянко
Java-разработчик в Playtika

Удаление элемента из списка ArrayList в Java

Статья из группы Java Developer
Привет! В прошлой лекции мы познакомились с классом 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='Пушок'}]
Это может быть очень удобно, если не хочется всегда держать в голове индекс нужного объекта. С обычным удалением вроде разобрались. Теперь давай представим такую ситуацию: мы хотим перебрать наш список элементов и удалить кота с определенным именем. Используем для этого специальный оператор цикла forfor 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. Точнее — почти все. В следующей лекции мы заглянем во “внутренности” этого класса, и посмотрим, что же там происходит во время совершения операций :) До встречи!
Комментарии (323)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Руслан Никитин Уровень 17
9 июня 2024
можно по грязному удалить одни элемент, пока итератор не чухнул, но только в качестве обучения)

        var list = new ArrayList<String>();
        list.add("Hi");
        list.add("Hello");
        for (String str : list) {
            if ("Hello".equals(str)) {
                list.remove(str);
                System.out.println("Working");
                break;
            }
        }
{Java_Shark} Уровень 20
14 марта 2024
++
Kaz Уровень 17
5 февраля 2024
у меня остались вопросы... - почему в принципе нельзя в самом переборе коллекции делать измения? - что потом с этим ирретатором делать? задача была удалить кота из коллекции, а вместо той коллекции ArrayList<Cat> cats мы создали объект Iterator<Cat> catIterator. Предположу, что коллекции нужно присвоить значение измененного ирреатора?
Anonymous #2963806 Уровень 17
15 декабря 2023
for (int i = 0; i < cats.size(); i++) { if (cats.get(i).name.equals("Бегемот")) { cats.remove(cats.get(i)); } } Если Вы примените простой цикл for, а не foreach, который под собой содержит итерацию (перебор) элементов без их изменения, то все будет работать и элемент удалится. Смотрите код выше. Foreach же, в основном служит для обхода всех элементов, с выводом их в определенный поток, ну или передаче элемента, в дальнейшую обработку, вне этого цикла - foreach. Именно foreach, не дает одновременно итерироваться и обрабатывать элементы коллекции, внутри цикла. А как мы видим из кода, простой цикл с итерацией по номеру элемента в коллекции, данные действия позволяет. Примите данное обстоятельство к сведению. Всем Удачи и Здоровья ! )))
keebrunner Уровень 23
11 декабря 2023
removeIf(): Принимает предикат (логическое условие) в качестве параметра и удаляет все элементы, для которых предикат возвращает true.

list.removeIf(element -> element.equals(valueToRemove));
removeAll(): Принимает другую коллекцию в качестве параметра и удаляет из текущей коллекции все элементы, которые присутствуют в переданной коллекции.

list.removeAll(List.of(valueToRemove));
Максим Li Уровень 40
30 ноября 2023
Интересно!
Krut Уровень 13
13 ноября 2023
Ошибка в коде: 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.length-1 Ошибка, Правильный вариант cats.length) { //перемещаем элементы к началу, чтобы пустая ячейка оказалась в конце cats[i-1] = cats[i]; cats[i] = null; } System.out.println(Arrays.toString(cats)); }
Denis Gritsay Уровень 41
21 сентября 2023
Тут Iterator<Cat> catIterator = cats.iterator();//создаем итератор while(catIterator.hasNext()) {//до тех пор, пока в списке есть элементы Cat nextCat = catIterator.next();//получаем следующий элемент if (nextCat.name.equals("Филипп Маркович")) { catIterator.remove();//удаляем кота с нужным именем } } System.out.println(cats); не понятно, почему используется такая конструкция - if (nextCat.name.equals("Филипп Маркович")) { а именно, почему используется name, вроде логично будет if (nextCat.equals("Филипп Маркович")) { то есть переменная nextCat содержит значение которое мы сравниваем с помощью метода equals с "Филипп Маркович"
Nostradamus Уровень 26
1 сентября 2023
Удачи в обучении пацы👍
Anomalous-Spectrum Уровень 16
19 августа 2023
Обычный цикл: "Я для тебя шутка?"