HashMap в Kotlin: как работать с коллекцией ключ-значение

Когда нужно быстро находить значение по ключу — номер телефона по имени, цену по артикулу или описание по коду — на помощь приходит HashMap. Это одна из самых часто используемых структур данных, и kotlin hashmap реализована очень удобно. В этой статье я простым языком объясню, как создать HashMap, добавить и получить элементы, проверить наличие ключа и перебрать все пары.


Что такое HashMap

HashMap — это коллекция, которая хранит пары «ключ — значение». Каждый ключ уникален, а значение может повторяться. Благодаря специальному внутреннему устройству поиск по ключу происходит очень быстро, почти мгновенно, независимо от размера коллекции. Для повседневных задач это лучший выбор.

Представьте обычный бумажный словарь: слово — ключ, его перевод — значение. Вы не листаете весь словарь, чтобы найти перевод, а открываете нужную букву. HashMap работает похоже, но вместо букв использует хеши — числа, которые вычисляются по ключу и указывают, в какой «ячейке» искать значение.


Создание HashMap

Самый простой способ — воспользоваться функцией mutableMapOf(). Она создаёт изменяемую HashMap и сразу позволяет добавить начальные пары через to. Если же нужна только для чтения, подойдёт mapOf().

val capitals = mutableMapOf(
    "Россия" to "Москва",
    "Франция" to "Париж",
    "Япония" to "Токио"
)

val readOnly: Map<String, String> = mapOf(
    "red" to "#ff0000",
    "green" to "#00ff00"
)

Если хотите создать пустую HashMap и наполнять её позже, можно указать типы ключа и значения явно:

val scores = mutableMapOf<String, Int>()
// или через конструктор
val empty = HashMap<String, Int>()

Добавление и изменение элементов

В изменяемой HashMap можно добавлять новые пары или обновлять существующие. Kotlin предлагает два равноценных способа: метод put() и краткий синтаксис квадратных скобок.

val map = mutableMapOf<String, Int>()
map.put("apple", 5)
map["banana"] = 3
map["apple"] = 10   // обновление

println(map) // {apple=10, banana=3}

Разницы между put и [] нет, выбирайте тот, который лучше читается в вашем коде.


Получение значения по ключу

Достать значение можно несколькими способами, и каждый полезен в своей ситуации.

  • [] — возвращает значение, если ключ найден, иначе null. Безопасный вызов ?. защищает от NullPointerException.
  • getValue() — выбрасывает исключение NoSuchElementException, если ключа нет. Используйте, когда ключ обязан быть.
  • getOrElse() — даёт значение по ключу, а если его нет — выполняет лямбду и возвращает её результат.
  • getOrDefault() — возвращает значение, а если ключ отсутствует — стандартное значение, которое вы указали.
val map = mapOf("a" to 1, "b" to 2)
println(map["a"])              // 1
println(map["c"])              // null
println(map.getValue("a"))     // 1
println(map.getOrElse("c") { 0 }) // 0
println(map.getOrDefault("c", -1)) // -1

Проверка наличия ключа или значения

Часто нужно просто узнать, есть ли определённый ключ в HashMap или встречается ли какое-то значение. Для этого существуют два метода.

val map = mapOf("x" to 10, "y" to 20)
println("x" in map)             // true, проверка ключа
println(map.containsKey("z"))   // false
println(map.containsValue(20))  // true

Оператор in — краткая форма вызова containsKey. Для проверки значения используйте containsValue.


Удаление элементов

Из изменяемой HashMap можно удалять отдельные ключи или очистить её полностью.

val map = mutableMapOf("a" to 1, "b" to 2, "c" to 3)
map.remove("b")
println(map) // {a=1, c=3}

map.clear()
println(map) // {}

Метод remove возвращает удалённое значение или null, если ключ отсутствовал.


Перебор всех пар

Чтобы обработать все элементы HashMap, удобно использовать цикл for с деструктуризацией или метод forEach.

val fruits = mapOf("apple" to 5, "banana" to 3, "cherry" to 7)

// через for с деструктуризацией
for ((name, count) in fruits) {
    println("$name: $count шт.")
}

// через forEach
fruits.forEach { (name, count) ->
    println("$name = $count")
}

Обратите внимание, что порядок обхода HashMap не гарантирован. Если важен порядок добавления, используйте LinkedHashMap.


Изменяемые и неизменяемые Map

Новички часто путают mapOf и mutableMapOf. Первая возвращает неизменяемый объект, в который нельзя добавить элементы после создания. Вторая — изменяемый, с методами put, remove, clear.

val immutable = mapOf("a" to 1)
// immutable["b"] = 2  // ошибка компиляции

val mutable = mutableMapOf("a" to 1)
mutable["b"] = 2      // работает

Старайтесь использовать неизменяемые коллекции по умолчанию — это защищает от случайных изменений и делает код предсказуемым.


Пара слов о null и производительности

HashMap в Kotlin разрешает один null-ключ и сколько угодно null-значений. Но лучше избегать null-ключей без крайней необходимости.

val map = mutableMapOf<String?, Int?>()
map[null] = null
println(map) // {null=null}

По умолчанию начальная вместимость HashMap — 16 ячеек, а коэффициент заполнения — 0.75. Это значит, что когда количество элементов достигнет 12, массив автоматически расширится. Для большинства приложений менять эти параметры не нужно.


Практический пример: подсчёт частоты слов

Давайте применим полученные знания. Напишем функцию, которая принимает список слов и возвращает HashMap, где ключ — слово, а значение — сколько раз оно встретилось.

fun countWords(words: List<String>): Map<String, Int> {
    val counter = mutableMapOf<String, Int>()
    for (word in words) {
        counter[word] = counter.getOrDefault(word, 0) + 1
    }
    return counter
}

val result = countWords(listOf("яблоко", "банан", "яблоко", "апельсин", "банан", "яблоко"))
println(result) // {яблоко=3, банан=2, апельсин=1}

Код читается просто: для каждого слова либо берём текущий счётчик и увеличиваем на 1, либо начинаем с нуля. HashMap идеально подходит для такой задачи.


Коротко о главном

Kotlin HashMap — это быстрый и удобный способ хранить данные в формате «ключ-значение». Вы научились создавать коллекцию через mutableMapOf(), добавлять элементы через [] и put, получать значения разными методами, проверять ключи и значения, удалять и перебирать пары. Освойте эти базовые операции — и работа с данными станет проще в любом проекте, от небольшого скрипта до серверного приложения. Для тренировки создайте HashMap с любимыми фильмами и рейтингами, добавьте пару записей и выведите их в цикле — это займёт пять минут, но даст полное понимание.