Remove: примеры (JAVA)

Разбор реализации remove в Java
Раздел: Коллекции (Collection Framework) - List
remove(int index): E

Описание и сигнатуры методов remove

В Java слово remove встречается в нескольких методах стандартной библиотеки. Основные варианты:

  • boolean Collection.remove(Object o) - удаляет один экземпляр элемента, равный o по equals. Возвращает true, если коллекция изменилась.
  • E List.remove(int index) - удаляет элемент по индексу и возвращает удалённое значение. При неверном индексе выбрасывает IndexOutOfBoundsException.
  • void Iterator.remove() - удаляет последний возвращённый итератором элемент. Должен вызываться только после next() и не более одного раза на итерацию; в противном случае возникает IllegalStateException. Может выбрасывать UnsupportedOperationException, если операция не поддерживается реализацией.
  • V Map.remove(Object key) - удаляет пару с ключом key и возвращает прежнее значение или null, если пары не было или прежнее значение было null. Чтобы отличить случаи, использовать containsKey.
  • boolean Map.remove(Object key, Object value) - удаляет пару только если ключ сопоставлен с указанным значением; возвращает true, если удаление произошло. Полезно для атомарной проверки и удаления.
  • boolean Collection.removeIf(Predicate<? super E> filter) - добавлён в Java 8; удаляет все элементы, соответствующие предикату; возвращает true, если коллекция изменилась.

Поведение сравнения элементов зависит от реализации коллекции: обычно используется equals для объектов. Некоторые реализации (например, специализированные структуры) могут иметь собственные правила. При работе с неизменяемыми или фиксированными коллекциями вызов методов удаления приводит к UnsupportedOperationException. При одновременной модификации из другого потока без синхронизации возможны ConcurrentModificationException или неконсистентные результаты.

Короткие примеры использования

Пример 1. Collection.remove(Object) - удаление по значению

List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
boolean removed = list.remove("b");
System.out.println(removed);
System.out.println(list);
true
[a, c]

Пример 2. List.remove(int) - удаление по индексу

List list2 = new ArrayList<>(Arrays.asList("a", "b", "c"));
String r = list2.remove(1);
System.out.println(r);
System.out.println(list2);
b
[a, c]

Пример 3. Iterator.remove() - корректное удаление во время итерации

List list3 = new ArrayList<>(Arrays.asList("x", "y", "z"));
Iterator it = list3.iterator();
while (it.hasNext()) {
  String s = it.next();
  if (s.equals("y")) {
    it.remove();
  }
}
System.out.println(list3);
[x, z]

Пример 4. Map.remove(key) и Map.remove(key, value)

Map map = new HashMap<>();
map.put("a", 1);
map.put("b", null);
Integer prev = map.remove("a");
System.out.println(prev);
Integer prevNull = map.remove("b");
System.out.println(prevNull);
boolean removedPair = map.remove("c", 3);
System.out.println(removedPair);
1
null
false

Пример 5. Collection.removeIf(Predicate) - удаление по условию

List nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
boolean any = nums.removeIf(n -> n % 2 == 0);
System.out.println(any);
System.out.println(nums);
true
[1, 3, 5]

Похожие методы в Java и их особенности

  • clear() - удаляет все элементы; предпочтительнее, когда нужно полностью опустошить коллекцию вместо поэлементного удаления.
  • removeAll(Collection<?> c) - удаляет все элементы, входящие в указанную коллекцию; удобна для массового удаления по множеству значений.
  • retainAll(Collection<?> c) - оставляет только элементы, входящие в c; эффективна при фильтрации наборов.
  • Stream.filter(...).collect(...) (Java 8+) - создаёт новую коллекцию с отфильтрованными элементами; полезно, если требуется оставлять исходную коллекцию нетронутой и работать в функциональном стиле.

Выбор зависит от требуемой семантики: для атомарного удаления пары в потокобезопасной ситуации подходит Map.remove(key, value), для массовой фильтрации без модификации исходной структуры - Stream, для очистки - clear().

Аналогичные операции в других языках

  • JavaScript

    Array.prototype.splice для удаления по индексу, Array.prototype.filter для фильтрации, Map.prototype.delete для Map.

    let arr = ['a','b','c'];
    let removed = arr.splice(1,1); // удаляет 1 элемент по индексу 1
    console.log(removed);
    console.log(arr);
    ['b']
    ['a','c']
  • Python

    list.remove(value) удаляет первое совпадение, list.pop(index) удаляет по индексу; del для удаления срезов; set.discard/ remove для множеств.

    lst = ['a','b','c']
    lst.remove('b')
    print(lst)
    ['a', 'c']
  • PHP

    unset($array[$index]) удаляет элемент по ключу; array_splice для сдвига элементов; для ассоциативных массивов unset удаляет пару.

    $a = ['a','b','c'];
    array_splice($a,1,1);
    print_r($a);
    Array
    (
        [0] => a
        [1] => c
    )
  • SQL

    DELETE FROM table WHERE condition - удаляет строки в базе, эквивалент масcовому remove для набора записей.

  • C#

    List.Remove(value), RemoveAt(index), Dictionary<K,V>.Remove(key). Синтаксис и семантика близки к Java.

    var list = new List<string> {"a","b","c"};
    list.Remove("b");
    Console.WriteLine(string.Join(",", list));
    a,c
  • Go

    Нет встроенной функции remove для срезов: используется комбинация append и срезов; для map используется delete(map, key).

    s := []int{1,2,3}
    s = append(s[:1], s[2:]...)
    fmt.Println(s)
    [1 3]
  • Kotlin

    MutableList.remove(value) и removeAt(index), MutableMap.remove(key) - API и поведение аналогичны Java, с дополнениями расширений и удобных функций.

  • Lua

    table.remove(t, index) удаляет элемент по индексу и сдвигает последующие элементы.

Отличия от Java: в некоторых языках удаление по индексу или по значению ведёт себя иначе при отсутствии элемента (вызов ошибки или молчаливый возврат), и есть различные способы атомарного удаления в конкурентной среде.

Типичные ошибки при использовании remove

  • Удаление из коллекции во время итерации с использованием for-each приводит к ConcurrentModificationException.
  • Вызов Iterator.remove() до first next() или при повторном remove() вызывает IllegalStateException.
  • Map.remove(key) возвращает null как для отсутствия пары, так и для наличия пары со значением null - возможная двусмысленность.
  • Попытка удалить из неизменяемой или фиксированной коллекции вызывает UnsupportedOperationException (например, список из Arrays.asList).
  • Collection.removeIf(null) или removeIf с null-предикатом приведёт к NullPointerException.

Примеры ошибок:

ConcurrentModificationException при удалении в for-each

List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
for (String s : list) {
  if (s.equals("b")) {
    list.remove(s); // вызовет ConcurrentModificationException
  }
}
Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
    ...

UnsupportedOperationException для Arrays.asList

List<String> fixed = Arrays.asList("a","b");
fixed.remove("a"); // UnsupportedOperationException
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.AbstractList.remove(AbstractList.java:...) 
    ...

IllegalStateException при неправильном использовании Iterator.remove()

Iterator<String> it = Arrays.asList("a","b").iterator();
it.remove(); // IllegalStateException, next() не вызывался
Exception in thread "main" java.lang.IllegalStateException
    at java.base/java.util.ArrayList$Itr.remove(ArrayList.java:...)
    ...

Изменения и добавления в последних версиях

  • removeIf появился в Java 8 как часть расширений коллекций для работы с лямбдами и предикатами; до этого массовую фильтрацию приходилось реализовывать вручную через итераторы или stream + collect.
  • Map.remove(key, value) как атомарная проверка и удаление стала широко использоваться с развитием конкурентных коллекций и утилит в Java 8 и далее.
  • В целом сигнатуры базовых методов remove остались стабильными, но появились удобные утилиты и улучшения для параллельной и функциональной работы с коллекциями.

Расширенные и редкие сценарии использования

1) Удаление дублей с сохранением порядка

Пример java
List<String> data = new ArrayList<>(Arrays.asList("a","b","a","c","b"));
Set<String> seen = new HashSet<>();
data.removeIf(s -> !seen.add(s));
System.out.println(data);
[a, b, c]

2) Атомарное удаление в ConcurrentHashMap по паре ключ-значение

Пример java
ConcurrentHashMap<String,Integer> cmap = new ConcurrentHashMap<>();
cmap.put("k", 1);
boolean removed = cmap.remove("k", 2); // вернёт false, ничего не удалит
System.out.println(removed);
removed = cmap.remove("k", 1); // вернёт true
System.out.println(removed);
false
true

3) Удаление элементов Map по условию на entrySet (без ConcurrentModification при использовании итератора)

Пример java
Map<String,Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 10);
map.put("c", 5);
map.entrySet().removeIf(e -> e.getValue() < 6);
System.out.println(map);
{b=10}

4) Удаление при итерации с ListIterator: возможность безопасно менять последовательность

Пример java
List<String> lst = new ArrayList<>(Arrays.asList("1","2","3"));
ListIterator<String> li = lst.listIterator();
while (li.hasNext()) {
  String v = li.next();
  if (v.equals("2")) {
    li.set("2-updated"); // обновление текущего элемента
    li.add("2.5"); // вставка после текущей позиции
    // можно вызвать li.remove() только если предыдущий метод next/previous был вызван
  }
}
System.out.println(lst);
[1, 2-updated, 2.5, 3]

5) CopyOnWriteArrayList - итераторы не бросают ConcurrentModification, но не отражают последующие модификации

Пример java
CopyOnWriteArrayList<String> cow = new CopyOnWriteArrayList<>(Arrays.asList("a","b","c"));
for (String s : cow) {
  if (s.equals("b")) {
    cow.remove(s); // безопасно, не бросит ConcurrentModification, но итератор по старому снимку
  }
}
System.out.println(cow);
[a, c]

6) Удаление сложных условий, комбинирование с Stream

Пример java
List<User> users = ...; // большая коллекция
// Оставить пользователей старше 18 и удалить остальных
users.removeIf(u -> u.getAge() <= 18);
// Альтернатива: собрать отфильтрованный список
List<User> adults = users.stream().filter(u -> u.getAge() > 18).collect(Collectors.toList());
(в зависимости от входных данных изменяется коллекция или формируется новый список)

7) Удаление элементов файловой системы - пример использования API вне коллекций

Пример java
Path p = Paths.get("temp.txt");
try {
  Files.deleteIfExists(p);
  System.out.println("deleted");
} catch (IOException e) {
  e.printStackTrace();
}
deleted (если файл существовал)

В каждом расширенном сценарии стоит учитывать семантику возвращаемых значений и особенности реализации коллекции: атомарность в конкурентной среде, поддержка операции удаления и поведение итераторов.

джава remove function comments

En
Remove Removes the element at the specified position in this list