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

Map.put в Java: описание и практические примеры
Раздел: Коллекции (Collection Framework) - Map
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 m = new TreeMap<>();
        m.put("a", 1);
        m.put(1, 2); // ClassCastException при сравнении String и Integer
    }
}
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 переработан механизм (использование сегментной блокировки заменено на более эффективные структуры), улучшена производительность конкурентных операций.

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

Несколько подробных примеров с пояснениями и результатами.

Пример java
// Пример: счётчик слов с использованием 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 упрощает логику обновления счётчика, выполняет вставку при отсутствии ключа и объединение при наличии.

Пример java
// Пример: ленивое создание вложенной структуры с 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]}
Пример java
// Пример: 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 выполняет вычисление атомарно для одного ключа, предотвращая двойную инициализацию.

Пример java
// Пример: условная замена с проверкой старого значения
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}
Пример java
// Пример: массовая вставка и возврат предыдущих значений
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 или конкурентные структуры.

джава Map.put function comments

En
Map.put Associates the specified value with the specified key in this map