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

Справка по методу containsKey
Раздел: Коллекции (Collection Framework) - Map
Map.containsKey(Object key): boolean

Общее описание Map.containsKey

Метод Map.containsKey(Object key) проверяет, содержит ли отображение (Map) ключ, равный заданному. Возвращается булево значение: true если имеется соответствие ключа, false в противном случае. Метод объявлен в интерфейсе java.util.Map и реализуется всеми стандартными реализациями коллекций.

Параметр:

  • key - объект ключа для проверки. Может быть null для тех реализаций, которые поддерживают null-ключи (например, HashMap, LinkedHashMap). Некоторые реализации (ConcurrentHashMap, TreeMap при естественном порядке) не допускают null и при передаче null выбрасывают исключение.

Возвращаемое значение:

  • boolean - true если в Map присутствует запись с ключом, который сравнивается с переданным с помощью метода equals (и для хеш-основных реализаций также используется hashCode); в противном случае false.

Поведение и сложность:

  • Для HashMap и аналогичных реализаций среднее время проверки - O(1), в худшем случае - O(n).
  • Для TreeMap время - O(log n) за счёт поиска по дереву.
  • Для ConcurrentMap реализация обеспечивает конкурентную безопасность, но некоторые операции могут иметь ограничения на null-ключи.

Особенности сравнения ключей:

  • Для большинства реализаций используется метод equals для сравнения ключей. В IdentityHashMap сравнение выполняется по идентичности (==), а не по equals.
  • Неправильные реализации методов equals и hashCode у объектов-ключей приводят к неверным результатам при поиске ключа.

Примеры использования containsKey

Примеры с выводом кода и результата.

1) Базовый пример с HashMap:

import java.util.*;

Map map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
System.out.println(map.containsKey("a"));
System.out.println(map.containsKey("c"));
true
false

2) Проверка null-ключа (HashMap допускает):

Map map = new HashMap<>();
map.put(null, 5);
System.out.println(map.containsKey(null));
true

3) TreeMap и null-ключ вызывает исключение (при естественном порядке):

Map map = new TreeMap<>();
map.put("x", 10);
System.out.println(map.containsKey(null));
Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.TreeMap.getEntry(TreeMap.java:...)
    ...

4) Отличие от get при значении null:

Map map = new HashMap<>();
map.put("k", null);
System.out.println(map.get("k") == null);
System.out.println(map.containsKey("k"));
System.out.println(map.get("missing") == null);
System.out.println(map.containsKey("missing"));
true
true
true
false

5) Пользовательский ключ с переопределёнными equals и hashCode:

class Key { String id; Key(String id){this.id=id;} 
public boolean equals(Object o){return o instanceof Key && ((Key)o).id.equals(id);} 
public int hashCode(){return id.hashCode();} }

Map map = new HashMap<>();
map.put(new Key("1"), "one");
System.out.println(map.containsKey(new Key("1")));
true

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

  • Map.get(Object key) - возвращает значение по ключу. При отсутствии возвращается null, что даёт неоднозначность, если значения могут быть null.
  • Map.containsValue(Object value) - проверяет наличие значения. Обычно дороже по времени (O(n)).
  • Map.keySet().contains(Object key) - эквивалент containsKey для стандартных реализаций, может быть удобен при работе с представлением ключей.
  • ConcurrentMap.putIfAbsent / computeIfAbsent - предпочтительнее в многопоточных сценариях вместо последовательной проверки containsKey и вставки, так как обеспечивают атомарность.

Когда что предпочтительнее:

  • Если требуется только проверка наличия ключа - containsKey.
  • Если нужно значение без неоднозначности null - использовать containsKey совместно с get, либо хранить Optional или использовать getOrDefault.
  • В конкурентных сценариях - методам атомических операций (putIfAbsent, computeIfAbsent) отдать предпочтение перед containsKey+put.

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

Ниже короткие примеры и комментарии для разных языков.

  • PHP - array_key_exists и isset. array_key_exists проверяет существование ключа даже если значение равно null, isset возвращает false для null-значений.
    $arr = ["a" => null];
    var_dump(array_key_exists("a", $arr));
    var_dump(isset($arr["a"]));
    bool(true)
    bool(false)
  • JavaScript - Map.prototype.has и оператор in для объектов. Map.has точен для Map; оператор in проверяет свойства объекта и унаследованные свойства.
    const m = new Map([["a",1]]);
    console.log(m.has("a"));
    console.log("a" in {a:1});
    true
    true
  • Python - оператор in или dict.__contains__. Возвращает True если ключ присутствует; dict.get возвращает None для отсутствия или для значения None.
    d = {"a": None}
    print("a" in d)
    print(d.get("a") is None)
    print("b" in d)
    True
    True
    False
  • SQL - присутствие ключа обычно проверяется через EXISTS или WHERE ... IN.
    -- пример
    SELECT EXISTS(SELECT 1 FROM table WHERE id = 5);
    -- возвращает true/false в зависимости от СУБД
  • C# - Dictionary<K,V>.ContainsKey и TryGetValue. TryGetValue безопаснее при возможных null-значениях и в многопоточных сценариях.
    var dict = new Dictionary
    True
    True
  • Lua - таблицы: наличие ключа проверяется через сравнение с nil. Если значение может быть nil, применяется метаполя или отдельная структура.
    t = {a = nil}
    print(t["a"] ~= nil)
    print(rawget(t, "a") == nil)
    
    false
    true
  • Go - idiomatic: value, ok := m[key]; ok показывает наличие ключа даже если значение zero-value.
    m := map[string]*int{"a": nil}
    _, ok := m["a"]
    fmt.Println(ok)
    _, ok2 := m["b"]
    fmt.Println(ok2)
    true
    false
  • Kotlin - Map.containsKey, работает аналогично Java. Kotlin предоставляет удобные расширения, например getValue или getOrElse.

Отличия от Java: в некоторых языках (PHP isset, Lua) проверка ложная при значении null; в Go проверка возвращает булев флаг отдельно; в JavaScript объекты и Map различаются по поведению при проверке ключей.

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

  • Ошибочное использование get для проверки присутствия: проверка map.get(key) == null не отличает отсутствие ключа от значения null. Пример выше показывает отличия.
  • Использование null в реализациях, которые не допускают null-ключи: например, ConcurrentHashMap и TreeMap при естественном порядке выбросят NullPointerException.
    Map map = new java.util.concurrent.ConcurrentHashMap<>();
    map.containsKey(null);
    Exception in thread "main" java.lang.NullPointerException
        at java.base/java.util.concurrent.ConcurrentHashMap.hash(ConcurrentHashMap.java:...)
        ...
  • Неправильная реализация equals/hashCode у ключей: если hashCode или equals меняются после вставки в HashMap, containsKey может вернуть false.
    class Key { String id; Key(String id){this.id=id;} 
    public int hashCode(){return id.length();} 
    public boolean equals(Object o){return o instanceof Key && ((Key)o).id.equals(id);} }
    
    Map m = new HashMap<>();
    Key k = new Key("ab");
    m.put(k, "v");
    // изменена внутренне id (симулируется)/или состояние, влияющее на hashCode
    k.id = "abcd";
    System.out.println(m.containsKey(k));
    false
  • ClassCastException при использовании TreeMap с несравнимыми ключами.
    Map map = new TreeMap();
    map.put("a",1);
    map.containsKey(10);
    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at java.base/java.util.TreeMap.compare(TreeMap.java:...)
        ...

Изменения в API и заметные отличия в новых версиях

Метод containsKey присутствует в интерфейсе java.util.Map с ранних версий платформы (Collections framework). За последние версии Java сам метод не претерпел изменений в сигнатуре и семантике.

Косвенные изменения затрагивают сопутствующие возможности API: с появлением Java 8 и выше добавлены методы getOrDefault, computeIfAbsent, forEach и другие функциональные методы, которые в ряде случаев заменяют комбинацию containsKey и последующих действий. В многопоточном коде предпочтение отдаётся атомарным операциям ConcurrentMap.

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

Несколько углублённых примеров с объяснениями.

1) Использование IdentityHashMap - сравнение ключей по ссылке:

Пример java
import java.util.*;
Map m = new IdentityHashMap<>();
String a1 = new String("x");
String a2 = new String("x");
m.put(a1, "v");
System.out.println(m.containsKey(a1));
System.out.println(m.containsKey(a2));
true
false

Объяснение: IdentityHashMap использует сравнение по ==, поэтому равные по содержимому строки, но разные объекты считаются разными ключами.

2) WeakHashMap и влияние сборки мусора на наличие ключа:

Пример java
import java.util.*;
Map m = new WeakHashMap<>();
Object key = new Object();
m.put(key, "v");
System.out.println(m.containsKey(key));
key = null;
System.gc(); // возможна очистка ключа
// небольшая задержка чтобы GC мог очистить
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(m.isEmpty());
true
true   // возможно true, если ключ был очищен сборщиком мусора

Объяснение: WeakHashMap хранит ключи с слабой ссылкой, поэтому после удаления внешней ссылки и выполнения GC запись может исчезнуть и containsKey вернёт false.

3) Атомарность в многопоточной вставке - гонка при containsKey + put:

Пример java
Map map = new java.util.concurrent.ConcurrentHashMap<>();
// поток A
if (!map.containsKey("k")) {
    // до put другой поток может вставить ту же запись
    map.put("k","v1");
}
// безопаснее:
map.putIfAbsent("k","v2");
// при использовании containsKey возможны дубликаты вставок от разных потоков
// putIfAbsent гарантирует, что первый вставивший останется

Объяснение: containsKey не обеспечивает атомарности с последующими операциями. В многопоточных сценариях лучше использовать putIfAbsent или computeIfAbsent.

4) Поиск с пользовательским сравнением через ключ-обёртку:

Пример java
// хранение по naturalHash, но поиск по другому критерию
class Wrapper { String id; Wrapper(String id){this.id=id;} 
public boolean equals(Object o){return o instanceof Wrapper && ((Wrapper)o).id.equalsIgnoreCase(id);} 
public int hashCode(){return id.toLowerCase().hashCode();} }
Map map = new HashMap<>();
map.put(new Wrapper("Ab"), "v");
System.out.println(map.containsKey(new Wrapper("ab")));
true

Объяснение: если требуется нечувствительный к регистру поиск, реализация equals/hashCode может это учесть.

5) Использование containsKey в stream-выражениях:

Пример java
Map map = Map.of("a",1,"b",2);
List keysToCheck = List.of("a","c");
List present = keysToCheck.stream()
    .filter(map::containsKey)
    .toList();
System.out.println(present);
[a]

Объяснение: метод можно передавать как ссылку на метод для компактной фильтрации коллекций ключей.

6) Нестандартный случай: Map с пользовательской стратегией равенства - пример для ConcurrentHashMap не возможен, но для других реализаций есть адаптеры, поэтому при выборе реализации важно учитывать требования к сравнению ключей.

джава Map.containsKey function comments

En
Map.containsKey Returns true if this map contains a mapping for the specified key