← Назад к вопросам

Хранит ли Set только уникальные значения

1.3 Junior🔥 261 комментариев
#Коллекции#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Хранит ли Set только уникальные значения

Краткий ответ

Да, Set хранит только уникальные значения. Это основное предназначение интерфейса Set в Java — представлять коллекцию, которая не содержит дублирующихся элементов. Однако это справедливо только при правильной реализации методов equals() и hashCode() в объектах, которые вы добавляете в Set.

Что такое Set?

import java.util.HashSet;
import java.util.Set;

public class SetBasics {
    public static void main(String[] args) {
        Set<String> fruits = new HashSet<>();
        
        // Добавляем элементы
        fruits.add("apple");
        fruits.add("banana");
        fruits.add("apple");  // Дубликат
        fruits.add("orange");
        
        System.out.println(fruits.size());  // Output: 3 (не 4!)
        System.out.println(fruits);  // Output: [apple, banana, orange] (без дубликата)
    }
}

Как Set определяет уникальность?

Set использует equals() и hashCode() для определения, содержит ли он уже такой элемент:

public class UniquenessCheck {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        
        String s1 = new String("Hello");
        String s2 = new String("Hello");
        
        // String правильно реализует equals() и hashCode()
        System.out.println(s1.equals(s2));  // true (содержимое одинаково)
        System.out.println(s1 == s2);      // false (разные объекты)
        
        set.add(s1);
        set.add(s2);
        
        System.out.println(set.size());  // Output: 1 (дубликат не добавлен)
        // Хотя это разные объекты, Set считает их равными
    }
}

Проблема: неправильная реализация equals/hashCode

Если вы не переопределите equals() и hashCode(), Set будет неправильно определять уникальность:

public class WrongEqualsHashCode {
    
    // ❌ НЕПРАВИЛЬНО - нет переопределения equals() и hashCode()
    public static class Person {
        String name;
        int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    public static void main(String[] args) {
        Set<Person> people = new HashSet<>();
        
        Person p1 = new Person("John", 30);
        Person p2 = new Person("John", 30);
        
        System.out.println(p1.equals(p2));  // false (использует Object.equals, сравнивает ссылки)
        
        people.add(p1);
        people.add(p2);
        
        System.out.println(people.size());  // Output: 2 (ОШИБКА!)
        // Хотя это люди с одинаковыми данными,
        // Set хранит ОБОИХ, потому что они разные объекты
    }
}

Правильная реализация

public class CorrectEqualsHashCode {
    
    // ✅ ПРАВИЛЬНО - переопределены equals() и hashCode()
    public static class Person {
        String name;
        int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Person person = (Person) obj;
            return age == person.age && Objects.equals(name, person.name);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    }
    
    public static void main(String[] args) {
        Set<Person> people = new HashSet<>();
        
        Person p1 = new Person("John", 30);
        Person p2 = new Person("John", 30);
        
        System.out.println(p1.equals(p2));  // true (содержимое одинаково)
        
        people.add(p1);
        people.add(p2);
        
        System.out.println(people.size());  // Output: 1 (правильно!)
        // Set корректно определил, что это один и тот же человек
    }
}

Различные реализации Set

public class SetImplementations {
    public static void main(String[] args) {
        // 1. HashSet — быстро (O(1) в среднем), не упорядочено
        Set<Integer> hashSet = new HashSet<>();
        hashSet.add(3);
        hashSet.add(1);
        hashSet.add(2);
        System.out.println(hashSet);  // [1, 2, 3] или другой порядок
        
        // 2. TreeSet — упорядочено (O(log n)), требует Comparable
        Set<Integer> treeSet = new TreeSet<>();
        treeSet.add(3);
        treeSet.add(1);
        treeSet.add(2);
        System.out.println(treeSet);  // [1, 2, 3] (всегда отсортирован)
        
        // 3. LinkedHashSet — упорядочено по вставке (O(1))
        Set<Integer> linkedSet = new LinkedHashSet<>();
        linkedSet.add(3);
        linkedSet.add(1);
        linkedSet.add(2);
        System.out.println(linkedSet);  // [3, 1, 2] (порядок вставки)
    }
}

Как работает HashSet

public class HashSetMechanism {
    
    // Упрощённая реализация HashSet
    public static class SimpleHashSet<E> {
        private HashMap<E, Object> map = new HashMap<>();
        private static final Object PRESENT = new Object();
        
        public boolean add(E e) {
            // Если элемент уже есть в map, вернёт существующий объект
            // если нет — добавит и вернёт null
            return map.put(e, PRESENT) == null;
        }
        
        public int size() {
            return map.size();
        }
    }
    
    public static void main(String[] args) {
        SimpleHashSet<String> set = new SimpleHashSet<>();
        
        boolean added1 = set.add("apple");   // true (добавлен)
        boolean added2 = set.add("apple");   // false (уже есть)
        
        System.out.println(set.size());  // Output: 1
    }
}

Set vs List

public class SetVsList {
    public static void main(String[] args) {
        // List — позволяет дубликаты
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("apple");  // Дубликат добавляется
        System.out.println(list.size());  // Output: 3
        System.out.println(list);  // [apple, banana, apple]
        
        // Set — не позволяет дубликаты
        Set<String> set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        set.add("apple");  // Дубликат НЕ добавляется
        System.out.println(set.size());  // Output: 2
        System.out.println(set);  // [apple, banana] или [banana, apple]
    }
}

Практический пример: Удаление дубликатов

public class RemovingDuplicates {
    public static void main(String[] args) {
        // Массив с дубликатами
        int[] numbers = {1, 2, 3, 2, 4, 1, 5, 3};
        
        // Способ 1: Использование HashSet
        Set<Integer> unique = new HashSet<>();
        for (int num : numbers) {
            unique.add(num);
        }
        System.out.println(unique);  // [1, 2, 3, 4, 5] (в каком-то порядке)
        
        // Способ 2: Использование Stream (Java 8+)
        List<Integer> list = Arrays.asList(1, 2, 3, 2, 4, 1, 5, 3);
        List<Integer> uniqueList = list.stream()
                                       .distinct()  // Удаляет дубликаты
                                       .collect(Collectors.toList());
        System.out.println(uniqueList);  // [1, 2, 3, 4, 5]
    }
}

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

public class SetPerformance {
    public static void main(String[] args) {
        // HashSet: O(1) среднее время
        Set<String> hashSet = new HashSet<>();
        hashSet.add("a");      // O(1)
        hashSet.remove("a");   // O(1)
        hashSet.contains("a"); // O(1)
        
        // TreeSet: O(log n) время
        Set<String> treeSet = new TreeSet<>();
        treeSet.add("a");      // O(log n)
        treeSet.remove("a");   // O(log n)
        treeSet.contains("a"); // O(log n)
        // Но гарантирует упорядоченность!
        
        // LinkedHashSet: O(1) время с сохранением порядка
        Set<String> linkedSet = new LinkedHashSet<>();
        linkedSet.add("a");      // O(1)
        linkedSet.remove("a");   // O(1)
        linkedSet.contains("a"); // O(1)
    }
}

Частая ошибка: изменение объекта в Set

public class DangerousSetUsage {
    
    public static class Person {
        String name;
        int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Person person = (Person) obj;
            return age == person.age && Objects.equals(name, person.name);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    }
    
    public static void main(String[] args) {
        Set<Person> people = new HashSet<>();
        
        Person john = new Person("John", 30);
        people.add(john);
        System.out.println(people.size());  // 1
        
        // ❌ ОПАСНО: изменяем объект в Set
        john.age = 31;  // Изменяем hashCode!
        // Теперь hashCode() вернёт другое значение,
        // и Set не сможет найти объект
        
        Person anotherJohn = new Person("John", 31);
        people.add(anotherJohn);  // Добавит как новый элемент!
        System.out.println(people.size());  // 2 (ОШИБКА!)
    }
}

Правило для Set

Уникальность в Set зависит от:
1. Правильной реализации equals() — сравнивает содержимое
2. Правильной реализации hashCode() — определяет позицию в таблице
3. Неизменяемости объектов (не менять поля после добавления в Set)

Если нарушить хотя бы одно — Set перестанет работать правильно

Таблица: Set операции

ОперацияHashSetTreeSetLinkedHashSet
add()O(1)O(log n)O(1)
remove()O(1)O(log n)O(1)
contains()O(1)O(log n)O(1)
ПорядокНетОтсортированПорядок вставки
null✅ Да❌ Нет✅ Да
Потокобезопасно❌ Нет❌ Нет❌ Нет

Итог

  1. Set хранит только уникальные значения — это его основная задача
  2. Уникальность зависит от equals() и hashCode() — переопределите их правильно
  3. HashSet — по умолчанию — быстрая O(1) операция
  4. TreeSet — для упорядоченности — O(log n) операции
  5. Не изменяйте объекты в Set — это нарушит структуру данных
  6. null значения — HashSet и LinkedHashSet поддерживают, TreeSet нет

Запомните: Set — это не просто коллекция, это гарантия уникальности, и эта гарантия зависит от того, насколько правильно вы реализовали equals() и hashCode().

Хранит ли Set только уникальные значения | PrepBro