Map.keySet: примеры (JAVA)
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) { Mapm = 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
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 для создания списка ключей (копия)
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) Потоковая обработка ключей
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
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
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) Создание независимого неизменяемого набора ключей
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()
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) Преобразование ключей в массив или список
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 и возможные способы его безопасного применения в различных сценариях.