Set и HashSet
Тема дорожной карты · Java
HashSet и интерфейс Set — важные части Java Collections framework, моделирующие математическое понятие множества — неупорядоченной коллекции уникальных элементов, — обеспечивая операции add, remove и contains со средней сложностью O(1), основанные на внутреннем использовании HashMap. Интерфейс Set расширяет Collection и гарантирует отсутствие дублирующих элементов (на основе контрактов equals и hashCode), с тремя основными реализациями: HashSet для неупорядоченной уникальности с лучшей средней производительностью, LinkedHashSet для множеств с сохранением порядка вставки и TreeSet для отсортированных множеств, упорядоченных по естественному порядку или пользовательскому Comparator. Корректный контракт equals/hashCode критически важен для поведения HashSet — если два объекта equal, они должны иметь одинаковый hashCode; нарушения вызывают молчаливые дубликаты и сбои contains, которые печально трудно отлаживать в коде Java Collections. HashSet и Set полностью интегрированы со Stream API (Collectors.toSet(), Collectors.toUnmodifiableSet()), Generics и фабричными методами Java 9+ (Set.of(a, b, c)), возвращающими компактные неизменяемые реализации множеств. Для конкурентного использования подходят ConcurrentSkipListSet (отсортированный, без блокировок) и Collections.newSetFromMap(new ConcurrentHashMap<>()), делая HashSet и Set правильным инструментом для дедупликации, проверки принадлежности и операций алгебры множеств в любом Java-приложении.
Как это работает
Set и HashSet даёт List (ArrayList, LinkedList), Set (HashSet, TreeSet, LinkedHashSet), Map (HashMap, TreeMap, LinkedHashMap, ConcurrentHashMap), Queue (ArrayDeque, PriorityQueue). Все реализуют Collection (Map отдельно). Streams API (stream(), filter(), map(), collect()) заменяет большинство явных циклов. Immutable factory-методы: List.of(...), Map.of(...), Set.of(...) — возвращают read-only view (бросают при мутации).
Когда применять
ArrayList по умолчанию вместо LinkedList — array-backed доступ драматически быстрее для почти любой реальной нагрузки. HashMap — кроме случаев, где важен порядок (LinkedHashMap для insertion order, TreeMap для sorted). ConcurrentHashMap — для shared-state map (никогда не оборачивайте HashMap в Collections.synchronizedMap в новом коде). Stream — для трансформаций; циклы — для side-effect-обработки.
Типичные ошибки
Ловушки Set и HashSet: выбор LinkedList для "быстрых insert" без замеров (cache-miss затмевает алгоритмическое преимущество); Map<String, ...> с mutable ключевыми объектами (hashCode меняется — потерянные entry); List.of(...) и попытка добавить позже (UnsupportedOperationException); concurrent modification в for-цикле (CME — используйте removeIf или Iterator).