Как работает интерфейс Set в Java?

«Как работает интерфейс Set в Java?» — вопрос из категории Java, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Set в Java — это коллекция (java.util.Set), которая не допускает дубликатов элементов. Его основная контрактная обязанность — гарантировать уникальность, что часто реализуется через методы equals() и hashCode() объектов.

Ключевые характеристики:

  1. Уникальность: Не может содержать двух элементов e1 и e2 таких, что e1.equals(e2) == true. Добавление дубликата просто игнорируется (метод add() возвращает false).
  2. Порядок: Не гарантирует никакого порядка элементов, если только не используется конкретная реализация, которая его обеспечивает (например, LinkedHashSet).
  3. Один null: Большинство реализаций допускают не более одного элемента null.

Основные реализации и их отличия:

Реализация Внутренняя структура Порядок Время выполнения (Big O) Особенности
HashSet Хэш-таблица Не гарантирован add(), remove(), contains(): O(1) в среднем Самая распространенная, требует корректных hashCode() и equals().
LinkedHashSet Хэш-таблица + связный список Порядок вставки (insertion-order) Немного медленнее HashSet, но тоже O(1) Полезен, когда нужна уникальность + предсказуемый порядок итерации.
TreeSet Красно-черное дерево Натуральный порядок или порядок заданный Comparator add(), remove(), contains(): O(log n) Элементы отсортированы. Реализует интерфейс SortedSet/NavigableSet.

Примеры использования:

import java.util.*;

public class SetExample {
    public static void main(String[] args) {
        // 1. HashSet - для быстрой проверки принадлежности
        Set<String> uniqueWords = new HashSet<>();
        uniqueWords.add("apple");
        uniqueWords.add("banana");
        boolean added = uniqueWords.add("apple"); // false, дубликат не добавлен
        System.out.println(uniqueWords); // [banana, apple] (порядок может быть любым)
        System.out.println("Contains 'apple': " + uniqueWords.contains("apple")); // true

        // 2. LinkedHashSet - сохраняем порядок добавления (например, для истории уникальных посещений)
        Set<Integer> visitOrder = new LinkedHashSet<>();
        visitOrder.add(100);
        visitOrder.add(200);
        visitOrder.add(100); // Игнорируется
        visitOrder.add(300);
        System.out.println("Visit order: " + visitOrder); // [100, 200, 300]

        // 3. TreeSet - автоматическая сортировка
        Set<Integer> sortedNumbers = new TreeSet<>();
        sortedNumbers.add(42);
        sortedNumbers.add(10);
        sortedNumbers.add(99);
        System.out.println("Sorted set: " + sortedNumbers); // [10, 42, 99]

        // Использование компаратора для обратной сортировки строк
        Set<String> reverseSorted = new TreeSet<>(Comparator.reverseOrder());
        reverseSorted.addAll(Arrays.asList("zebra", "apple", "mango"));
        System.out.println("Reverse sorted: " + reverseSorted); // [zebra, mango, apple]
    }
}

Когда использовать Set?

  • Когда необходимо гарантировать уникальность коллекции (уникальные ID, логины, теги).
  • Для эффективной проверки наличия элемента (contains() работает за O(1) у HashSet).
  • Для выполнения операций над множествами: объединение (addAll), пересечение (retainAll), разность (removeAll).