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

Справка по Map.get в Java
Раздел: Коллекции (Collection Framework) - Map
Map.get(Object key): V

Описание Map.get в Java

Метод Map.get является базовой операцией интерфейса java.util.Map. Подпись метода в интерфейсе выглядит как:

V get(Object key)

Назначение метода - получить значение, связанное с указанным ключом. Используется при чтении данных из отображения по ключу.

Аргументы и поведение:

  • key - объект ключа, тип может отличаться от параметра типа K, поскольку метод принимает Object. На практике тип ключа должен быть совместим с используемым сравнением (equals/компаратор).
  • Если ключ присутствует в Map, возвращается соответствующее значение типа V.
  • Если ключ отсутствует, возвращается null. Это делает невозможным однозначное различие между "ключ отсутствует" и "ключ присутствует, но его значение равно null" без дополнительной проверки.
  • Некоторые реализации допускают и null-ключи и null-значения (например, HashMap). Другие не допускают null-ключов (например, ConcurrentHashMap) либо не допускают null-значений (зависит от реализации), а TreeMap с естественным порядком вызывает NullPointerException при null-ключе.

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

  • Значение типа V, связанное с ключом.
  • null при отсутствии соответствия или при явном хранении null в качестве значения.

Исключения и особенности реализации:

  • ClassCastException может возникнуть, если реализация требует приведения ключа к совместимому типу (например, TreeMap с Comparator), а переданный объект несовместим.
  • NullPointerException может быть брошен реализацией, если передан null и реализация не поддерживает null-ключи (например, ConcurrentHashMap).
  • Метод является неблокирующим в большинстве реализаций, но семантика при конкурентном доступе зависит от конкретной реализации (ConcurrentHashMap обеспечивает слабую консистентность чтений).

Полезные дополнения: начиная с Java 8 появились сопутствующие методы (getOrDefault, computeIfAbsent, putIfAbsent и др.), которые позволяют обрабатывать отсутствие ключа более выразительно.

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

Пример 1. Получение существующего значения и отсутствующего ключа:

import java.util.HashMap;
import java.util.Map;

Map map = new HashMap<>();
map.put("a", "alpha");
String v1 = map.get("a");
String v2 = map.get("b");
System.out.println(v1);
System.out.println(v2 == null ? "null" : v2);
alpha
null

Пример 2. Null-ключ в HashMap и в TreeMap:

import java.util.HashMap;
import java.util.TreeMap;

Map h = new HashMap<>();
h.put(null, "nil");
System.out.println(h.get(null));

Map t = new TreeMap<>();
// У TreeMap с естественным порядком следующий вызов приведет к исключению
// t.put(null, "nil");
nil
// Для TreeMap попытка вставить null-ключ приводит к NullPointerException

Пример 3. ConcurrentHashMap не допускает null-ключи и null-значения:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

ConcurrentMap cm = new ConcurrentHashMap<>();
// cm.put(null, "x"); // бросит NullPointerException
System.out.println(cm.get("missing"));
null

Пример 4. Различие между get и containsKey:

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

Аналоги и смежные методы в Java

  • getOrDefault(Object key, V defaultValue) - возвращает значение или заданный defaultValue, если ключ отсутствует. Удобен для подстановки значений вместо проверки на null.
  • containsKey(Object key) - проверяет наличие ключа в Map. Используется для отличия "значение null" от "ключ отсутствует".
  • putIfAbsent(K key, V value) (ConcurrentMap) - ставит значение, только если ключ отсутствует, полезен при конкурентной инициализации.
  • computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) - удобен для ленивой инициализации значений на основе ключа и безопаснее в некоторых случаях, чем get + put.
  • remove(Object key) - возвращает удаленное значение, полезно, когда требуется забрать значение и удалить запись сразу.

Выбор между ними зависит от задачи: если нужно заменить null результат значением по умолчанию - getOrDefault; чтобы отличить отсутствие ключа от null - containsKey; для безопасной многопоточной инициализации - computeIfAbsent или putIfAbsent у ConcurrentMap.

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

PHP (ассоциативные массивы и функции):

$arr = ['a' => 'alpha'];
echo $arr['a'];
// при обращении к несуществующему ключу - предупреждение и null-подобное поведение
echo isset($arr['b']) ? $arr['b'] : 'null';
// или оператор null coalescing (PHP 7+)
echo $arr['b'] ?? 'default';
alpha
null
default

JavaScript (Map и объекты):

const m = new Map([['a','alpha']]);
console.log(m.get('a'));
console.log(m.get('b')); // undefined

const obj = {a: 'alpha'};
console.log(obj['a']);
console.log(obj['b']); // undefined
alpha
undefined
alpha
undefined

Python (dict):

d = {'a': 'alpha'}
print(d.get('a'))
print(d.get('b'))
print(d.get('b', 'default'))
alpha
None
default

SQL (SELECT):

-- В SQL получение значения по ключу соответствует выборке строки
SELECT value FROM table WHERE key = 'a';
-- Набор результатов с нулевым числом строк или одной строкой

C# (Dictionary):

var dict = new Dictionary<string,string>{{"a","alpha"}};
string v;
if (dict.TryGetValue("a", out v)) Console.WriteLine(v);
Console.WriteLine(dict.ContainsKey("b") ? dict["b"] : "null");
alpha
null

Lua (table):

t = {a = 'alpha'}
print(t['a'])
print(t['b']) -- nil
alpha
nil

Go (map):

m := map[string]string{"a":"alpha"}
if v, ok := m["a"]; ok { fmt.Println(v) }
if v, ok := m["b"]; ok { fmt.Println(v) } else { fmt.Println("not found") }
alpha
not found

Kotlin (Map):

val m = mapOf("a" to "alpha")
println(m["a"]) // alpha
println(m["b"]) // null
println(m.getOrDefault("b", "default")) // default
alpha
null
default

Краткие отличия от Java:

  • Во многих динамических языках обращение к несуществующему ключу возвращает undefined/nil/None, а не бросает исключение, и обычно есть удобные операторы для подстановки значения по умолчанию.
  • В C# рекомендуемый паттерн - TryGetValue, возвращающий флаг успешности, что схоже по смыслу с containsKey + get в Java, но объединено в один вызов.
  • В Kotlin и Python есть get-методы с возможность указать значение по умолчанию; в Java аналог - getOrDefault.

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

  • Ожидание исключения при отсутствии ключа. На практике get возвращает null, поэтому логика должна учитывать это.
  • Неправильная проверка значения: сравнение результата get с null не отличает "ключ отсутствует" от "значение равно null". В таких случаях следует использовать containsKey.
  • Передача null в реализации, которая не поддерживает null-ключи (ConcurrentHashMap) - приводит к NullPointerException.
  • ClassCastException при использовании TreeMap с несовместимым ключом или при кастинге ключей при сравнении.
  • Игнорирование конкурентных условий: в многопоточной среде значение, полученное через get, может устареть к моменту использования.

Пример ошибки 1 - ожидание исключения:

Map<String,String> m = new HashMap<>();
String v = m.get("no");
// Ожидание исключения приведет к неверному коду
// Нужна проверка на null
System.out.println(v);
null

Пример ошибки 2 - NullPointerException при использовании ConcurrentHashMap:

import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String,String> cm = new ConcurrentHashMap<>();
// cm.put(null, "x"); // бросит NullPointerException
// cm.get(null); // тоже бросит NullPointerException
// NullPointerException при попытке использовать null-ключ с ConcurrentHashMap

Пример ошибки 3 - ClassCastException в TreeMap:

import java.util.TreeMap;
TreeMap<Integer,String> t = new TreeMap<>();
t.put(1, "one");
// Object k = "str";
// t.get(k); // ClassCastException при попытке сравнить несовместимые типы
// ClassCastException

Изменения и сопутствующие улучшения в Java

Существует несколько изменений в экосистеме Map, которые влияют на работу с get, хотя сам метод не менял сигнатуру:

  • Java 8: добавлены методы по умолчанию в интерфейсе Map, в частности getOrDefault, computeIfAbsent, computeIfPresent, merge. Они упрощают обработку отсутствующих ключей и уменьшат количество проверок после get.
  • Java 9: появились фабричные методы Map.of и Map.copyOf для создания неизменяемых отображений. Эти реализации не допускают null-ключи и null-значения, что влияет на поведение get (null означает отсутствие значения, но вставить null нельзя).
  • ConcurrentHashMap в Java 8 и далее обеспечивает улучшенную производительность и семантику, но по-прежнему не поддерживает null-ключи и null-значения.

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

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

Пример 1. Кеш с использованием computeIfAbsent вместе с get-проверкой:

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

Map<String,String> cache = new HashMap<>();
String key = "k";
// Безопасная ленивальная инициализация
String value = cache.computeIfAbsent(key, k -> expensiveCalc(k));
System.out.println(value);

static String expensiveCalc(String k) {
    return "computed-" + k;
}
computed-k

Пояснение: computeIfAbsent избегает двойного поиска (get + put) и делает инициализацию атомарной для не concurrent Map.

Пример 2. Оборачивание результата get в Optional для явной работы с отсутствием:

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

Map<String,String> m = new HashMap<>();
m.put("a", null);
Optional<String> opt = Optional.ofNullable(m.get("a"));
System.out.println(opt.isPresent()); // true, значение null - превращается в Optional.empty()? -> ofNullable(null) даст empty
System.out.println(opt.orElse("default"));
false
default

Пояснение: Optional.ofNullable позволяет унифицировать обработку null, но не различает отсутствие ключа и значение null без дополнительной проверки containsKey.

Пример 3. WeakHashMap и сборка мусора ключей:

Пример java
import java.util.WeakHashMap;
import java.util.Map;

Map<Object,String> wm = new WeakHashMap<>();
Object key = new Object();
wm.put(key, "v");
System.out.println(wm.get(key));
key = null;
System.gc();
// после сборки мусора ключ может быть удален, get вернет null
System.out.println(wm.size());
v
0 // возможный результат после GC, зависит от поведения сборщика мусора

Пример 4. Получение значения и безопасное использование в многопоточной среде:

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

ConcurrentMap<String,Integer> counts = new ConcurrentHashMap<>();
// атомарное увеличение счетчика
counts.merge("k", 1, Integer::sum);
Integer cnt = counts.get("k");
System.out.println(cnt);
1

Пояснение: при конкурентном доступе предпочтительнее использовать атомарные методы (merge, compute, putIfAbsent) вместо get + put из-за возможных состояний гонки.

Пример 5. Кастомная реализация Map и особое поведение get:

Пример java
import java.util.AbstractMap;
import java.util.Set;

class SingleEntryMap extends AbstractMap<String,String> {
    private final String k = "only";
    private final String v = "value";
    public Set<Entry<String,String>> entrySet() { return Set.of(Map.entry(k,v)); }
    public String get(Object key) { return k.equals(key) ? v : null; }
}

Map<String,String> m = new SingleEntryMap();
System.out.println(m.get("only"));
System.out.println(m.get("other"));
value
null

Пояснение: переопределение get позволяет оптимизировать специфические структуры данных, но следует соблюдать контракт Map.

Пример 6. Использование get вместе с потоком данных (Stream) и Optional для безопасной композиции:

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

Map<String,String> m = new HashMap<>();
m.put("a","alpha");
Optional.ofNullable(m.get("a"))
        .map(String::toUpperCase)
        .ifPresent(System.out::println);

Optional.ofNullable(m.get("b")).map(String::toUpperCase).ifPresentOrElse(
    s -> System.out.println(s),
    () -> System.out.println("no value")
);
ALPHA
no value

джава Map.get function comments

En
Map.get Returns the value to which the specified key is mapped