Map.remove: примеры (JAVA)

Удаление элементов из Map в Java
Раздел: Коллекции (Collection Framework) - Map
Map.remove(Object key): V

Описание метода Map.remove

Интерфейс java.util.Map предоставляет методы для удаления элементов по ключу. Основные варианты:

  • V remove(Object key) - удаляет запись с указанным ключом и возвращает предыдущее значение, связанное с этим ключом, либо null, если ключ отсутствовал или если связанное значение равно null. Метод доступен с ранних версий Java.
  • boolean remove(Object key, Object value) - удаляет запись только в том случае, если текущая пара ключ-значение равна переданному значению (по equals). Возвращает true, если удаление выполнено, иначе false. Этот метод появился в интерфейсе Map как default-метод в Java 8. Аналогичные семантики были в ConcurrentMap с момента его появления.

Аргументы и возвращаемые значения:

  • Аргумент key принимается как Object. Реализация использует equals и, при деревоподобных реализациях (например, TreeMap), сравнение через Comparator или Comparable.
  • Вариант remove(key) возвращает значение типа V до удаления или null. Поскольку null может означать два состояния (отсутствие ключа или сохранённое null-значение), перед проверкой на отсутствие иногда применяется containsKey.
  • Вариант remove(key, value) возвращает boolean. Удаление выполняется атомарно относительно реализации Map и особенно полезно для конкурентных коллекций.

Особенности поведения на разных реализациях:

  • HashMap разрешает null для ключей и значений; возвращаемое null нужно интерпретировать с учётом containsKey.
  • ConcurrentHashMap не допускает null для ключей и значений; вызов с null приводит к NullPointerException.
  • TreeMap опирается на порядок, определённый Comparator/Comparable, и удаление использует сравнение ключей.
  • Немодифицируемые карты (Collections.unmodifiableMap, Map.of и т. п.) выбрасывают UnsupportedOperationException при попытке удаления.

Типичные случаи использования: освобождение памяти, управление состоянием кэша, условное удаление при совпадении значения, безопасная работа в многопоточном окружении через ConcurrentMap.

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

Пример 1. Удаление существующей записи и вывод удалённого значения.

import java.util.HashMap;

public class Main1 {
    public static void main(String[] args) {
        HashMap map = new HashMap<>();
        map.put("a", 1);
        Integer old = map.remove("a");
        System.out.println(old);
        System.out.println(map.containsKey("a"));
    }
}
1
false

Пример 2. Удаление ключа, которого нет.

HashMap m = new HashMap<>();
String v = m.remove("missing");
System.out.println(v);
null

Пример 3. Различие между null-значением и отсутствием ключа.

HashMap m = new HashMap<>();
m.put("k", null);
System.out.println(m.remove("k"));
System.out.println(m.containsKey("k"));
null
false

Пример 4. Условное удаление с двумя аргументами.

HashMap m = new HashMap<>();
m.put("x", 10);
boolean removed = m.remove("x", 10);
System.out.println(removed);
System.out.println(m.containsKey("x"));
true
false

Пример 5. Попытка условного удаления при несовпадении значения.

HashMap m = new HashMap<>();
m.put("y", 5);
boolean removed = m.remove("y", 7);
System.out.println(removed);
System.out.println(m.get("y"));
false
5

Пример 6. Поведение в ConcurrentHashMap (null недопустим).

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap cm = new ConcurrentHashMap<>();
cm.put("k", 2);
System.out.println(cm.remove("k"));
2

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

  • Map.clear() - удаляет все записи сразу; используется, когда требуется очистить коллекцию целиком вместо выборочной операции.
  • Map.remove(key, value) - рассмотрен выше; предпочтителен в ситуациях, требующих условного и атомарного удаления.
  • ConcurrentMap.remove(K, V) - та же семантика для конкурентных коллекций; предпочтительна при работе в многопоточной среде.
  • Iterator.remove() (через entrySet().iterator()) - безопасный способ удаления при итерации по коллекции, предотвращающий ConcurrentModificationException для неконкурентных реализаций.

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

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

  • JavaScript: Map.prototype.delete(key) возвращает boolean; для обычного объекта используется delete obj[key], результат - boolean. Пример:
const map = new Map([['a',1]]);
console.log(map.delete('a'));
console.log(map.has('a'));
true
false
  • Python: dict.pop(key, default) возвращает значение или default; есть dict.popitem() и del dict[key].
d = {'a':1}
print(d.pop('a'))
print(d.get('a'))
1
None
  • PHP: для массивов и словарей используется unset($arr[$key]); не возвращает удалённое значение. Для получения значения сначала выполняется чтение, затем unset.
$map = ['a' => 1];
$val = $map['a'];
unset($map['a']);
var_dump($val, array_key_exists('a', $map));
int(1)
bool(false)
  • C#: Dictionary<K,V>.Remove(key) возвращает boolean; TryGetValue применяется, если нужно получить значение и удалить.
var dict = new Dictionary{{"a",1}};
int val;
if (dict.TryGetValue("a", out val)) {
    dict.Remove("a");
}
Console.WriteLine(val);
Console.WriteLine(dict.ContainsKey("a"));
1
False
  • Go: в map удаление через delete(m, key); не возвращает значение. Если требуется значение, сначала присваивается, затем delete.
m := map[string]int{"a":1}
v, ok := m["a"]
delete(m, "a")
fmt.Println(v, ok)
_, exists := m["a"]
fmt.Println(exists)
1 true
false
  • Kotlin: MutableMap.remove(key) возвращает предыдущее значение или null, семантика близка к Java; есть remove(key, value) для условного удаления.
  • Lua: удаление реализуется через присвоение nil: t[k] = nil; не возвращает прежнего значения напрямую, поэтому его читают до удаления.

Отличия от Java: во многих языках удаление не возвращает значение напрямую (Go, PHP, Lua), поэтому чтение и удаление выполняются в два шага. Java и Kotlin поддерживают возвращаемое предыдущее значение; JavaScript Map возвращает boolean.

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

  • Путаница с возвращаемым null. Если реализация допускает null-значения (например, HashMap), remove(key) возвращает null как для отсутствия ключа, так и для существующего ключа со значением null. Решение - проверка containsKey.
  • UnsupportedOperationException. Попытка удаления из немодифицируемой карты (Map.of, Collections.unmodifiableMap) приводит к исключению.
  • NullPointerException в ConcurrentHashMap при передаче null в методы удаления. В многопоточных реализациях null не допускается.
  • ConcurrentModificationException. Модификация Map во время обхода через for-each без использования итератора приводит к исключению у неконкурентных реализаций.

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

UnsupportedOperationException:

import java.util.Map;

Map m = Map.of("a",1);
m.remove("a");
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:XXX)
	... 

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

import java.util.HashMap;

HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
for (Integer k : map.keySet()) {
    map.remove(k);
}
System.out.println("done");
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:XXX)
	... 

NullPointerException в ConcurrentHashMap:

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap cm = new ConcurrentHashMap<>();
cm.remove(null);
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.concurrent.ConcurrentHashMap.checkKey(ConcurrentHashMap.java:XXX)
	... 

Изменения в поведении метода в новых версиях Java

  • В Java 8 в интерфейс Map был добавлен default-метод remove(Object key, Object value), что дало единую условную операцию удаления для всех реализаций Map без необходимости использования ConcurrentMap.
  • ConcurrentMap и ConcurrentHashMap имели методы условного удаления ранее, но перенос в Map как default улучшил единообразие API.
  • Поведение по отношению к null и исключениям остается зависимым от конкретной реализации; в новых релизах изменения в производительности или внутренней реализации (например, оптимизации HashMap) не влияют на контракт remove.

Расширенные и редкие сценарии применения Map.remove

Сценарий 1. Безопасное удаление во время итерации с помощью итератора.

Пример java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class SafeRemove {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("a",1);
        map.put("b",2);
        Iterator> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry e = it.next();
            if (e.getValue() % 2 == 0) {
                it.remove(); // безопасное удаление
            }
        }
        System.out.println(map);
    }
}
{a=1}

Сценарий 2. Условное удаление в многопоточном окружении с ConcurrentHashMap.

Пример java
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentRemove {
    public static void main(String[] args) {
        ConcurrentHashMap chm = new ConcurrentHashMap<>();
        chm.put("k", 100);
        boolean removed = chm.remove("k", 100); // атомарно
        System.out.println(removed);
    }
}
true

Сценарий 3. Атомарная проверка и удаление без race condition через remove(key, value) на обычной Map в Java 8+

Пример java
Map map = new HashMap<>();
map.put("x", 1);
// если только одно поточное окружение, это безопасно; в многопоточном - использовать ConcurrentMap
boolean ok = map.remove("x", 1);
System.out.println(ok);
true

Сценарий 4. Удаление элементов по условию через removeIf на entrySet - удобный способ массовой фильтрации.

Пример java
Map map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.entrySet().removeIf(e -> e.getValue() % 2 == 0);
System.out.println(map);
{a=1}

Сценарий 5. Комбинация compute и remove для условной логики, когда требуется возвращать предыдущее значение.

Пример java
Map map = new HashMap<>();
map.put("c", 3);
Integer prev = map.remove("c");
if (prev != null) {
    // дополнительная логика с prev
    System.out.println("Удалено: " + prev);
}
System.out.println(map);
Удалено: 3
{}

Сценарий 6. Использование remove при работе с TreeMap и компаратором: удаление происходит с учётом сравнения ключей.

Пример java
import java.util.Comparator;
import java.util.TreeMap;

TreeMap tm = new TreeMap<>(Comparator.reverseOrder());
tm.put("a",1);
tm.put("b",2);
System.out.println(tm.remove("a"));
System.out.println(tm);
1
{b=2}

Сценарий 7. Удаление с возвратом и логированием - шаблон для кэшей.

Пример java
Map cache = new HashMap<>();
cache.put("user:1", "data1");
String removed = cache.remove("user:1");
if (removed != null) {
    System.out.println("Evicted cache: " + removed);
}
Evicted cache: data1

Сценарий 8. Обход типичных ошибок: проверка на null и неподдерживаемые операции перед удалением.

Пример java
Map m = Map.of("a","v");
if (!(m instanceof java.util.Collections.UnmodifiableMap)) {
    // в реальном коде лучше перехватить UnsupportedOperationException
    m.remove("a");
}
-- в immutable карте удаление всё равно приведёт к исключению --

джава Map.remove function comments

En
Map.remove Removes the mapping for a key from this map if it is present