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

Map.keySet - назначение и демонстрация
Раздел: Коллекции (Collection Framework) - Map
Map.keySet: Set

Описание метода

Метод Map.keySet() возвращает представление множества ключей ассоциативной коллекции, реализующей интерфейс java.util.Map. Это метод без аргументов. Возвращаемый тип - Set<K>, где K - тип ключей карты.

Особенности поведения:

  • Возвращаемое множество является представлением (view) исходной карты: изменения множества отражаются в карте и наоборот, если операция поддерживается конкретной реализацией.
  • Операции чтения: contains, size, isEmpty, iterator работают как ожидалось и делегируют действия карте.
  • Операции удаления через множество (например, remove, removeIf, iterator().remove(), clear) удаляют соответствующие пары ключ-значение из карты, если реализация множества их поддерживает.
  • Операция добавления (add, addAll) как правило не поддерживается: у множества нет информации о значениях для новых ключей, поэтому вызов чаще всего приводит к UnsupportedOperationException. Исключением могут быть специальные реализации (например, ConcurrentHashMap.newKeySet() для создания набора).
  • Поведение относительно null ключей зависит от конкретной реализации Map: HashMap допускает null, ConcurrentHashMap не допускает.
  • Для упорядоченных карт (например, TreeMap) возвращаемое множество может реализовывать дополнительные интерфейсы, такие как SortedSet или NavigableSet, и сохранять порядок ключей.
  • В многопоточных картах (например, ConcurrentHashMap) итераторы обычно являются слабосогласованными (weakly consistent): они не бросают ConcurrentModificationException, но могут не отражать сразу все изменения. Поддержка iterator().remove() различается по реализации.

Сигнатура: Set<K> keySet(). Аргументы отсутствуют. Возвращаемое значение - сет ключей, связанный с картой.

Короткие примеры

Пример 1: перебор ключей у HashMap

import java.util.*;
class Example1 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("a", 1);
    m.put("b", 2);
    for (String k : m.keySet()) System.out.println(k);
  }
}
a
b

Пример 2: удаление через итератор

import java.util.*;
class Example2 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("a", 1);
    m.put("b", 2);
    Iterator it = m.keySet().iterator();
    while (it.hasNext()) {
      String k = it.next();
      if (k.equals("a")) it.remove();
    }
    System.out.println(m);
  }
}
{b=2}

Пример 3: попытка добавить ключ в keySet у HashMap (ошибка)

import java.util.*;
class Example3 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("a", 1);
    Set ks = m.keySet();
    ks.add("c"); // обычно UnsupportedOperationException
  }
}
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractCollection.add(AbstractCollection.java:XXX)
	... (stack trace)

Пример 4: упорядоченная карта - TreeMap

import java.util.*;
class Example4 {
  public static void main(String[] args) {
    Map m = new TreeMap<>();
    m.put(3, "c"); m.put(1, "a"); m.put(2, "b");
    for (Integer k : m.keySet()) System.out.println(k);
  }
}
1
2
3

Похожие подходы в Java

  • Map.entrySet() - представление пар ключ-значение. Предпочтительнее при необходимости одновременно читать ключ и значение без дополнительного обращения к карте. Итерация по entrySet эффективнее, если нужен доступ к значениям.
  • Map.values() - представление значений. Используется, когда интересует только коллекция значений.
  • map.forEach(BiConsumer) - функциональный способ обхода пары ключ-значение. Удобен для боковых эффектов и компактного кода, но не дает представление в виде множества.
  • Streams (map.keySet().stream()) - удобен для фильтрации, преобразований и параллельной обработки ключей.

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

  • JavaScript: для объекта используется Object.keys(obj) - возвращает массив строк-ключей; для Map - map.keys(), который возвращает итератор. Пример:
    const m = new Map([["a",1],["b",2]]);
    for (const k of m.keys()) console.log(k);
    a
    b
  • Python: dict.keys() возвращает view-объект, отражающий изменения словаря, похож на Java keySet. Пример:
    d = {"a":1, "b":2}
    ks = d.keys()
    print(list(ks))
    d.pop("a")
    print(list(ks))
    ['a', 'b']
    ['b']
  • PHP: array_keys($arr) возвращает новый массив ключей, это не view; изменения исходного массива не отразятся.
    $a = ["a"=>1, "b"=>2];
    print_r(array_keys($a));
    Array
    (
        [0] => a
        [1] => b
    )
    
  • C#: у Dictionary<K,V> свойство Keys возвращает Dictionary.KeyCollection, представляющую ключи и отражающую изменения словаря.
  • Go: нет встроенного view; ключи получают перебором map: for k := range m { ... }. Коллекция ключей обычно создается копированием в срез.
  • Kotlin: свойство Map.keys возвращает Set и ведёт себя как view для изменяемых карт; схоже с Java.
  • Lua: таблицы перебираются for k,v in pairs(t), отдельного view-объекта для ключей нет.

Отличие от Java: в ряде языков (PHP, Go, JavaScript Object.keys) возвращается копия списка ключей, а не view. В Python и C# возвращается представление, поведение максимально близкое к Java keySet.

Типичные ошибки и исключения

  • UnsupportedOperationException - попытка добавить ключ через keySet.add() для реализаций, где добавление не предусмотрено. Пример и результат были показаны в разделе примеров.
  • ConcurrentModificationException - при одновременной модификации карты во время итерации по keySet с помощью другого потока или другой части кода (для не-конкурентных реализаций, например HashMap). Пример:
    import java.util.*;
    class CMEExample {
      public static void main(String[] args) {
        Map m = new HashMap<>();
        m.put("a",1); m.put("b",2);
        for (String k : m.keySet()) {
          m.put("c", 3); // изменение во время итерации
        }
      }
    }
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:XXX)
    	... (stack trace)
  • NullPointerException - возможен при использовании реализации карты, не допускающей null, при попытке вставить null-ключ, но это не связано напрямую с keySet.
  • Iterator remove на ConcurrentHashMap - у некоторых многопоточных реализаций итератор может не поддерживать remove(), что приведет к UnsupportedOperationException. Рекомендуется проверять документацию конкретной реализации.

Изменения в последних версиях Java

  • Основная семантика Map.keySet() осталась стабильной на протяжении многих версий Java. Ключевые изменения в API Map в Java 8 и выше были связаны с добавлением default-методов (forEach, computeIfAbsent и т. п.), что позволило обходиться без явного использования keySet в некоторых сценариях.
  • В Java 9/10 появились фабричные методы Map.of и Map.copyOf, которые создают неизменяемые карты; у таких карт keySet() возвращает неизменяемое множество, и попытка модифицировать его вызывает UnsupportedOperationException.
  • В Java 10 добавлен Set.copyOf, упрощающий создание неизменяемого набора из ключей: Set.copyOf(map.keySet()).

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

1) Массовое удаление элементов по условию через keySet().removeIf

Пример java
import java.util.*;
class Adv1 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("a",1); m.put("b",2); m.put("c",3);
    m.keySet().removeIf(k -> k.startsWith("b"));
    System.out.println(m);
  }
}
{a=1, c=3}

2) Использование keySet для создания списка ключей (копия)

Пример java
import java.util.*;
class Adv2 {
  public static void main(String[] args) {
    Map m = Map.of("x",10,"y",20); // неизменяемая карта
    Set keysCopy = new HashSet<>(m.keySet());
    System.out.println(keysCopy);
  }
}
[x, y]

3) Потоковая обработка ключей

Пример java
import java.util.*;
import java.util.stream.Collectors;
class Adv3 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("apple", 3); m.put("banana", 5); m.put("apricot", 2);
    Set filtered = m.keySet().stream()
      .filter(k -> k.startsWith("a"))
      .collect(Collectors.toSet());
    System.out.println(filtered);
  }
}
[apple, apricot]

4) Специфика для TreeMap и NavigableMap

Пример java
import java.util.*;
class Adv4 {
  public static void main(String[] args) {
    NavigableMap tm = new TreeMap<>();
    tm.put(1, "a"); tm.put(2, "b"); tm.put(3, "c");
    // navigableKeySet предоставляет дополнительные операции (спуск, поднаборы)
    NavigableSet ks = ((TreeMap)tm).navigableKeySet();
    System.out.println(ks.descendingSet());
  }
}
[3, 2, 1]

5) Особые возможности ConcurrentHashMap

Пример java
import java.util.*;
import java.util.concurrent.*;
class Adv5 {
  public static void main(String[] args) {
    ConcurrentHashMap chm = new ConcurrentHashMap<>();
    chm.put("a",1); chm.put("b",2);
    Set ks = chm.keySet();
    ks.remove("a"); // удаляет отображение из карты в потокобезопасном режиме
    System.out.println(chm);
  }
}
{b=2}

Примечание: итераторы ConcurrentHashMap слабосогласованы; у них могут быть ограничения по remove() у конкретной реализации итератора.

6) Создание независимого неизменяемого набора ключей

Пример java
import java.util.*;
class Adv6 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("k1", 1);
    Set immutableKeys = Set.copyOf(m.keySet()); // Java 10+
    System.out.println(immutableKeys);
  }
}
[k1]

7) Использование view для синхронного удаления: вызов map.keySet().clear() эквивалентен map.clear()

Пример java
import java.util.*;
class Adv7 {
  public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put("a",1); m.put("b",2);
    m.keySet().clear();
    System.out.println(m);
  }
}
{}

8) Преобразование ключей в массив или список

Пример java
import java.util.*;
class Adv8 {
  public static void main(String[] args) {
    Map m = Map.of("a",1,"b",2);
    String[] arr = m.keySet().toArray(new String[0]);
    System.out.println(Arrays.toString(arr));
  }
}
[a, b]

Эти расширенные примеры демонстрируют гибкость view-объекта keySet и возможные способы его безопасного применения в различных сценариях.

джава Map.keySet function comments

En
Map.keySet Returns a Set view of the keys contained in this map