Map.put: примеры (JAVA)
Map.put(K key, V value): VОписание метода Map.put в Java
В интерфейсе java.util.Map объявлен метод V put(K key, V value). Метод сохраняет значение value по ключу key в отображении и возвращает предыдущее значение, связанное с этим ключом, либо null, если ключ ранее отсутствовал в карте или прежнее значение было null.
Аргументы и возвращаемое значение:
- K key: ключ, по которому сохраняется значение. Реализация может ограничивать допустимые ключи (например,
TreeMapтребует сопоставимости ключей,ConcurrentHashMapне допускаетnull). - V value: сохраняемое значение. Допустимость
nullзависит от реализации. - return V: предыдущее значение, ассоциированное с ключом, или
nullпри его отсутствии или при том, что прежним значением былоnull.
Поведение в популярных реализациях:
HashMap: допускает одинnullключ и несколькоnullзначений. Операция в среднем O(1).LinkedHashMap: какHashMapс предсказуемым порядком итерации.TreeMap: ключи должны быть сравнимы;nullключ недопустим (в стандартной реализации); операции O(log n).ConcurrentHashMap: не допускаетnullключей и значений; предназначен для многопоточности и обеспечивает консистентность при конкурентных операциях.
Особенности:
- Вызов может бросить
UnsupportedOperationExceptionдля неизменяемых карт (например, карты, возвращённойMap.of()илиCollections.unmodifiableMap). - При использовании
TreeMapс несравнимыми ключами илиnullключом возможныClassCastExceptionилиNullPointerException. - Наличие дополнительных методов в интерфейсе Map (начиная с Java 8) расширяет варианты обновления значений:
putIfAbsent,compute,merge,replaceи др.
Короткие примеры использования
Примеры с выводом результата в консоль. Код на Java.
// Пример 1: HashMap, поведение put
import java.util.HashMap;
import java.util.Map;
public class Ex1 {
public static void main(String[] args) {
Map m = new HashMap<>();
System.out.println(m.put("a", 1)); // null
System.out.println(m.put("a", 2)); // 1
System.out.println(m); // {a=2}
}
}
null
1
{a=2}
// Пример 2: TreeMap и null ключ
import java.util.Map;
import java.util.TreeMap;
public class Ex2 {
public static void main(String[] args) {
Map m = new TreeMap<>();
m.put(null, 1); // бросит NullPointerException в стандартной реализации
}
}
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.ComparableTimSort.indexOf(ComparableTimSort.java:...)
// Пример 3: ConcurrentHashMap не допускает null
import java.util.concurrent.ConcurrentHashMap;
public class Ex3 {
public static void main(String[] args) {
ConcurrentHashMap m = new ConcurrentHashMap<>();
m.put("k", 10); // OK
// m.put(null, 1); // бросит NullPointerException
System.out.println(m.put("k", 20)); // 10
}
}
10
Похожие методы в Java и их отличия
Внутри Java есть дополнительные методы для работы с сохранением/заменой значений:
- putIfAbsent(K, V): сохраняет значение только если ключ отсутствует; возвращает предыдущее значение или
null. Полезно для ленивой инициализации. - replace(K, V) и replace(K, oldV, newV): заменяют значение только если ключ присутствует или присутствует с указанным старым значением.
- putAll(Map): массовая вставка всех пар из другой карты.
- compute / computeIfAbsent / computeIfPresent / merge: функциональные операции, позволяющие атомарно вычислять новые значения на основе старых. Часто предпочтительны при вычислениях с учетом предыдущего состояния.
Выбор зависит от задачи: для простого присваивания предпочтительно put. Для условной и атомарной замены - putIfAbsent, replace или compute. Для объединения значений по ключу при группировке удобен merge.
Аналоги в других языках и ключевые отличия
Краткие сравнения с примерами. Результаты представлены ожидаемыми выводами.
PHP (ассоциативные массивы)
$m = [];
$m['a'] = 1; // присвоение
$prev = isset($m['a']) ? $m['a'] : null;
$m['a'] = 2;
var_dump($prev); var_dump($m['a']);
NULL int(2)
JavaScript (Map и объект literal)
const m = new Map();
console.log(m.set('a', 1)); // возвращает сам Map
console.log(m.set('a', 2));
console.log(m.get('a'));
Map(1) { 'a' => 1 }
Map(1) { 'a' => 2 }
2
Python (dict)
m = {}
prev = m.get('a')
m['a'] = 1
print(prev) # None
m['a'] = 2
print(m['a'])
None 2
SQL (INSERT/UPDATE)
-- PostgreSQL: вставка или обновление
INSERT INTO t(key, value) VALUES ('a', 1)
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
-- результат: в таблице будет ключ 'a' со значением 1 или обновленное значение
(операция в базе данных, в результате запись создана или обновлена)
C# (Dictionary)
var d = new Dictionary<string,int>();
int prev;
if (d.TryGetValue("a", out prev)) Console.WriteLine(prev); else Console.WriteLine("null");
d["a"] = 1;
// d.Add("a", 2); // бросит исключение если ключ уже есть
Console.WriteLine(d["a"]);
null 1
Go (map)
m := make(map[string]int)
prev, ok := m["a"]
fmt.Println(prev, ok) // 0 false
m["a"] = 1
fmt.Println(m["a"])
0 false 1
Kotlin (MutableMap)
val m = mutableMapOf()
println(m.put("a", 1)) // null
println(m.put("a", 2)) // 1
null 1
Отличия от Java:
- В JavaScript Map.set возвращает сам объект Map, а в Java put возвращает предыдущее значение.
- В Python и Go присваивание не возвращает предыдущее значение, но есть способы получить предыдущее явно.
- В C# индексатор присваивания не возвращает предыдущего значения, но есть TryGetValue и методы для условного добавления.
- В SQL операции записи выражаются через INSERT/UPDATE/UPSERT и возвращают статус выполнения, а не предыдущее значение в оперативной памяти.
Типичные ошибки при использовании put с примерами
Частые проблемы и причины их появления.
// 1. UnsupportedOperationException для неизменяемой карты
import java.util.Map;
import java.util.Collections;
public class Err1 {
public static void main(String[] args) {
Map m = Collections.unmodifiableMap(Map.of("a",1));
m.put("b",2); // UnsupportedOperationException
}
}
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableMap.put(Collections.java:...)
// 2. NullPointerException в ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
public class Err2 {
public static void main(String[] args) {
ConcurrentHashMap m = new ConcurrentHashMap<>();
m.put(null, 1); // NullPointerException
}
}
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:...)
// 3. ClassCastException и несравнимые ключи в TreeMap
import java.util.Map;
import java.util.TreeMap;
public class Err3 {
public static void main(String[] args) {
Map
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Comparable
Рекомендации при решении ошибок:
- Проверка типа карты перед записью (модульные проверки, документация реализации).
- Для многопоточного доступа использование ConcurrentHashMap или внешней синхронизации.
- Избегание изменений карты во время итерации без соответствующей синхронизации.
Изменения и эволюция
Ключевые изменения, влияющие на использование put и аналогичных операций:
- Java 1.2: введение коллекций и интерфейса
Map. - Java 5: обобщения (generics) улучшили безопасность типов у
Map<K,V>. - Java 8: добавлены дефолтные методы в интерфейс
Map-putIfAbsent,compute,computeIfAbsent,computeIfPresent,merge,replace. Это расширило возможности атомарного и функционального обновления значений. - Java 9: добавлена фабрика
Map.ofдля создания неизменяемых карт; такие карты не поддерживаютputи бросаютUnsupportedOperationException. - ConcurrentHashMap: в Java 8 переработан механизм (использование сегментной блокировки заменено на более эффективные структуры), улучшена производительность конкурентных операций.
Расширенные и нестандартные примеры использования
Несколько подробных примеров с пояснениями и результатами.
// Пример: счётчик слов с использованием merge
import java.util.HashMap;
import java.util.Map;
public class Adv1 {
public static void main(String[] args) {
String[] words = {"a","b","a","c","b","a"};
Map counts = new HashMap<>();
for (String w : words) {
counts.merge(w, 1, Integer::sum);
}
System.out.println(counts);
}
}
{a=3, b=2, c=1}
Пояснение: merge упрощает логику обновления счётчика, выполняет вставку при отсутствии ключа и объединение при наличии.
// Пример: ленивое создание вложенной структуры с putIfAbsent
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Adv2 {
public static void main(String[] args) {
Map();
// добавить значение в список по ключу
m.putIfAbsent("k", new ArrayList<>());
m.get("k").add("value1");
// при конкурентном доступе лучше использовать computeIfAbsent
System.out.println(m);
}
}
{k=[value1]}
// Пример: computeIfAbsent для потокобезопасной ленивой инициализации (с учетом небезопасности в HashMap)
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Adv3 {
public static void main(String[] args) {
Map safe = new ConcurrentHashMap<>();
String v = safe.computeIfAbsent("key", k -> expensiveInit(k));
System.out.println(v);
}
static String expensiveInit(String k) { return "val-for-" + k; }
}
val-for-key
Пояснение: computeIfAbsent с ConcurrentHashMap выполняет вычисление атомарно для одного ключа, предотвращая двойную инициализацию.
// Пример: условная замена с проверкой старого значения
import java.util.HashMap;
import java.util.Map;
public class Adv4 {
public static void main(String[] args) {
Map m = new HashMap<>();
m.put("x", 1);
boolean ok = m.replace("x", 1, 2); // true, если старое значение равно 1
System.out.println(ok + " " + m);
}
}
true {x=2}
// Пример: массовая вставка и возврат предыдущих значений
import java.util.HashMap;
import java.util.Map;
public class Adv5 {
public static Map putAllWithPrev(Map dest, Map src) {
Map prev = new HashMap<>();
for (Map.Entry e : src.entrySet()) {
prev.put(e.getKey(), dest.put(e.getKey(), e.getValue()));
}
return prev;
}
public static void main(String[] args) {
Map a = new HashMap<>(); a.put("a",1);
Map b = new HashMap<>(); b.put("a",2); b.put("b",3);
System.out.println(putAllWithPrev(a,b)); // {a=1, b=null}
System.out.println(a); // {a=2, b=3}
}
}
{a=1, b=null}
{a=2, b=3}
Пояснения и советы:
- Для частых конкурентных обновлений счётчиков и агрегаций предпочтительна использование специализированных примитивных или конкурентных коллекций.
- При хранении mutable-объектов в значениях важно учитывать побочные эффекты: put сохраняет ссылку, а не копию.
- При необходимости атомарной логики обновления лучше использовать
compute,mergeили конкурентные структуры.