JavaRush /Курсы /JAVA 25 SELF /Set: HashSet и TreeSet, уникальность элементов

Set: HashSet и TreeSet, уникальность элементов

JAVA 25 SELF
26 уровень , 3 лекция
Открыта

1. Введение

Давайте начнём с жизненного примера. Представьте, что вы организуете вечеринку и составляете список гостей. Вы рассылаете приглашения, а потом выясняется, что один и тот же человек оказался в списке дважды (или даже трижды — ну любит он вечеринки!). Если вы используете обычный список (List), такие дубли легко могут появиться. Но если бы у вас была коллекция, которая сама не позволяла бы добавить одного и того же гостя дважды — жизнь стала бы проще.

Вот тут и вступает в игру коллекция Set — множество.

Set — это коллекция, которая хранит только уникальные элементы. Если попытаться добавить уже существующий элемент, он просто не добавится (и никто не обидится).

Интерфейс Set: основные свойства

В Java Set — это интерфейс, который определяет поведение коллекции без дубликатов. Он наследуется от интерфейса Collection, а значит, поддерживает такие операции, как добавление (add), удаление (remove), проверка наличия элемента (contains) и перебор.

Ключевые особенности:

  • В Set не может быть двух одинаковых элементов.
  • Элементы могут храниться в произвольном порядке — зависит от конкретной реализации.
  • Нет индексов: нельзя обратиться к элементу по номеру, как в списке.

Синтаксис объявления

Set<String> guests = new HashSet<>();

2. HashSet: быстро, просто, без порядка

HashSet — самая популярная реализация интерфейса Set. Она основана на хеш-таблице (как и HashMap, только без пары «ключ-значение», а просто с уникальными значениями). Главное достоинство — быстрота операций добавления, удаления и поиска.

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

Представьте себе ящик, в который вы складываете вещи. Чтобы быстро понять, есть ли в нём уже что-то похожее, каждая вещь получает свой «номер» — хеш-код. Когда вы добавляете элемент в HashSet, сначала вычисляется этот хеш-код. Если такого ещё не встречалось, элемент спокойно кладётся в коллекцию. Если же хеш уже есть, то дополнительно проверяется равенство через equals(). И только если объекты действительно совпадают, новый элемент не добавляется.

То есть HashSet автоматически заботится об уникальности: два одинаковых объекта в нём не появятся.

Интересный момент: если вы работаете со своими собственными классами и хотите хранить их в HashSet, придётся переопределить методы equals() и hashCode(). Без этого коллекция может вести себя непредсказуемо — вроде бы одинаковые объекты будут считаться разными.

Основные методы HashSet

Set<String> guests = new HashSet<>();

guests.add("Иван");
guests.add("Мария");
guests.add("Пётр");
guests.add("Иван"); // Дубликат! Не добавится.

System.out.println(guests); // [Иван, Мария, Пётр] — порядок может быть любым

guests.remove("Пётр"); // Удаляем элемент
System.out.println(guests.contains("Мария")); // true
System.out.println(guests.size()); // 2

Давайте попробуем это в коде

Допустим, в нашем приложении мы хотим хранить уникальные имена задач, чтобы не было двух задач с одинаковым названием:

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

public class UniqueTasksDemo {
    public static void main(String[] args) {
        Set<String> tasks = new HashSet<>();
        tasks.add("Сделать домашку по Java");
        tasks.add("Погладить кота");
        tasks.add("Сделать домашку по Java"); // Дубликат!

        System.out.println("Список задач:");
        for (String task : tasks) {
            System.out.println("- " + task);
        }
        // В списке будет только две задачи, дубликат не добавится
    }
}

3. TreeSet: порядок важен!

Иногда нам нужно не просто уникальность, но и отсортированный набор элементов. Например, хочется видеть имена гостей по алфавиту, а не в случайном порядке. Для этого есть TreeSet.

TreeSet — это реализация интерфейса Set, которая хранит элементы в отсортированном порядке (по возрастанию). Она основана на структуре «красно-чёрное дерево».

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

import java.util.Set;
import java.util.TreeSet;

public class SortedGuestsDemo {
    public static void main(String[] args) {
        Set<String> guests = new TreeSet<>();
        guests.add("Владимир");
        guests.add("Алексей");
        guests.add("Екатерина");
        guests.add("Алексей"); // Дубликат!

        System.out.println("Гости (по алфавиту):");
        for (String guest : guests) {
            System.out.println("- " + guest);
        }
        // Вывод:
        // - Алексей
        // - Владимир
        // - Екатерина
    }
}

Обратите внимание: Если вы добавляете дубликат, он не появится в множестве. Всё, как и должно быть!

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

  • Когда нужен отсортированный набор уникальных элементов.
  • Когда важен быстрый поиск, но не критична скорость добавления (работает чуть медленнее, чем HashSet).
  • Если элементы — ваши собственные классы, они должны быть «сравнимыми» (реализовывать интерфейс Comparable) или вы должны предоставить свой Comparator.

4. Полезные нюансы

HashSet vs TreeSet: что выбрать?

Критерий HashSet TreeSet
Порядок хранения Не гарантируется Отсортирован по возрастанию
Скорость операций Быстрее (O(1)) Медленнее (O(log n))
Требования к типу Любой (достаточно equals()/hashCode()) Comparable или Comparator
Типичные сценарии Когда нужен быстрый доступ к уникальным элементам Когда важен упорядоченный вывод/обход

Особенности работы с Set

  • Нет индексов. В отличие от List, у Set нет метода get(int index). Если нужен доступ по индексу — используйте List.
  • Нет дубликатов. Если вы пытаетесь добавить элемент, который уже есть, он не добавится. Метод add вернёт false.
  • Не гарантируется порядок (кроме TreeSet). В HashSet порядок элементов может отличаться при каждом запуске. Если нужен порядок добавления, используйте LinkedHashSet.
  • Null-значения.
    • HashSet позволяет хранить один элемент null.
    • TreeSet не позволяет добавлять null без специального Comparator, иначе будет NullPointerException.

5. Типичные задачи для Set

Удаление дубликатов из списка

Допустим, у нас есть список студентов, где некоторые записаны по два раза. Нужно оставить только уникальные имена:

import java.util.*;

public class RemoveDuplicatesDemo {
    public static void main(String[] args) {
        List<String> students = Arrays.asList("Анна", "Игорь", "Анна", "Мария", "Игорь", "Павел");

        Set<String> uniqueStudents = new HashSet<>(students);

        System.out.println("Уникальные студенты: " + uniqueStudents);
        // Порядок не гарантируется!
    }
}

Если нужен отсортированный результат — используйте TreeSet:

Set<String> sortedUniqueStudents = new TreeSet<>(students);
System.out.println("Уникальные студенты (по алфавиту): " + sortedUniqueStudents);

Проверка уникальности (например, логина пользователя)

Set<String> usedLogins = new HashSet<>();
usedLogins.add("student1");
usedLogins.add("java_lover");

String newLogin = "student1";
if (usedLogins.contains(newLogin)) {
    System.out.println("Такой логин уже занят!");
} else {
    System.out.println("Логин свободен!");
}

Перебор элементов множества

Перебор осуществляется с помощью цикла for-each:

for (String name : uniqueStudents) {
    System.out.println(name);
}

6. Типичные ошибки при работе с Set

Ошибка №1: Ожидание определённого порядка элементов в HashSet. Многие новички удивляются, почему элементы множества выводятся в «странном» порядке. Это нормально — HashSet не гарантирует порядок. Если нужен порядок добавления — используйте LinkedHashSet, если нужна сортировка — TreeSet.

Ошибка №2: Попытка обратиться к элементу по индексу. Иногда пытаются написать что-то вроде set.get(0). Так нельзя: Set не поддерживает индексацию. Нужен доступ по индексу — берите List.

Ошибка №3: Хранение изменяемых объектов. Если вы храните объекты, которые могут менять поля, участвующие в equals()/hashCode(), после изменения таких полей элемент может «потеряться» для множества. Делайте элементы неизменяемыми или не меняйте идентифицирующие поля.

Ошибка №4: Ожидание, что дубликаты будут добавляться. Добавление одного и того же элемента несколько раз не увеличит размер множества — дубликаты игнорируются, метод add вернёт false.

Ошибка №5: Использование примитивных типов. Запись вроде Set<int> не скомпилируется. Используйте классы-обёртки: Set<Integer>, Set<Double> и т. д.

1
Задача
JAVA 25 SELF, 26 уровень, 3 лекция
Недоступна
Список участников мероприятия по алфавиту 🤵‍♀️🤵‍♂️
Список участников мероприятия по алфавиту 🤵‍♀️🤵‍♂️
1
Задача
JAVA 25 SELF, 26 уровень, 3 лекция
Недоступна
Оптимизация списка покупок 🛒✨
Оптимизация списка покупок 🛒✨
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ