← Назад к вопросам
Хранит ли 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 операции
| Операция | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| 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 | ✅ Да | ❌ Нет | ✅ Да |
| Потокобезопасно | ❌ Нет | ❌ Нет | ❌ Нет |
Итог
- Set хранит только уникальные значения — это его основная задача
- Уникальность зависит от equals() и hashCode() — переопределите их правильно
- HashSet — по умолчанию — быстрая O(1) операция
- TreeSet — для упорядоченности — O(log n) операции
- Не изменяйте объекты в Set — это нарушит структуру данных
- null значения — HashSet и LinkedHashSet поддерживают, TreeSet нет
Запомните: Set — это не просто коллекция, это гарантия уникальности, и эта гарантия зависит от того, насколько правильно вы реализовали equals() и hashCode().