Преподаватель: Ваш преподаватель ОАиП
Сайт: gadget59.ru
Множество (set) — это математическая концепция, описывающая_collection (коллекцию, набор) уникальных объектов. Уникальных — значит, что каждый элемент встречается в множестве только один раз.
В математике множества обозначаются фигурными скобками: {1, 2, 3, 4, 5}. Важно понимать:
{1, 2, 3} — это то же самое, что {3, 1, 2}.{1, 2, 2, 3} — это некорректная запись множества. Правильно: {1, 2, 3}.В программировании множества используются для решения множества практических задач:
В Java множества представлены интерфейсом Set, который находится в пакете java.util. Интерфейс Set наследуется от интерфейса Collection (коллекция), но добавляет главное ограничение: все элементы должны быть уникальными.
import java.util.Set;
import java.util.HashSet;
// Создаём пустое множество строк
Set<String> fruits = new HashSet<>();
// Добавляем элементы
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Апельсин");
fruits.add("Яблоко"); // Дубликат! Не добавится
System.out.println(fruits); // [Банан, Апельсин, Яблоко]
System.out.println(fruits.size()); // 3 (не 4!)
add() просто вернёт false и не добавит элемент. Ошибки не будет — множество просто проигнорирует повтор.
HashSet — это самая распространённая реализация интерфейса Set. Внутри она использует структуру данных, которая называется хеш-таблица (hash table).
Как работает хеш-таблица? Когда вы добавляете элемент, Java вычисляет его хеш-код (hash code) — специальное число, которое служит «адресом» элемента в памяти. Благодаря этому поиск, добавление и удаление происходят очень быстро — практически мгновенно.
import java.util.HashSet;
import java.util.Set;
Set<Integer> numbers = new HashSet<>();
numbers.add(5);
numbers.add(10);
numbers.add(15);
numbers.add(5); // Дубликат, не добавится
System.out.println(numbers); // [5, 10, 15] (порядок не гарантирован!)
System.out.println(numbers.contains(10)); // true
System.out.println(numbers.contains(20)); // false
LinkedHashSet — это вариант HashSet, который сохраняет порядок вставки элементов. То есть при выводе элементы будут идти в том же порядке, в котором вы их добавляли.
Под капотом LinkedHashSet использует и хеш-таблицу (для быстрого поиска), и связный список (для сохранения порядка). Это немного медленнее, чем обычный HashSet, но зато предсказуемо.
import java.util.LinkedHashSet;
import java.util.Set;
Set<String> days = new LinkedHashSet<>();
days.add("Понедельник");
days.add("Вторник");
days.add("Среда");
days.add("Четверг");
days.add("Пятница");
System.out.println(days);
// [Понедельник, Вторник, Среда, Четверг, Пятница]
// Порядок сохранён!
TreeSet — это реализация Set, которая автоматически сортирует элементы в естественном порядке (для чисел — по возрастанию, для строк — по алфавиту).
Под капотом TreeSet использует структуру данных красно-чёрное дерево (red-black tree) — это сбалансированное двоичное дерево поиска. Благодаря этому операции выполняются за время O(log n) — чуть медленнее, чем HashSet, но всё равно очень быстро.
import java.util.Set;
import java.util.TreeSet;
Set<Integer> numbers = new TreeSet<>();
numbers.add(50);
numbers.add(10);
numbers.add(30);
numbers.add(20);
numbers.add(40);
System.out.println(numbers); // [10, 20, 30, 40, 50]
// Элементы отсортированы!
// Дополнительные методы TreeSet:
System.out.println(((TreeSet<Integer>)numbers).first()); // 10
System.out.println(((TreeSet<Integer>)numbers).last()); // 50
Comparable). Все стандартные типы (Integer, String, Double) уже это делают.
| Характеристика | ⚡ HashSet | 🔗 LinkedHashSet | 🌳 TreeSet |
|---|---|---|---|
| Внутренняя структура | Хеш-таблица | Хеш-таблица + связный список | Красно-чёрное дерево |
| Порядок элементов | ❌ Не гарантирован | ✅ По порядку вставки | ✅ Отсортирован |
| Скорость add/contains/remove | O(1) — очень быстро | O(1) — очень быстро | O(log n) — быстро |
| Дополнительные методы | — | — | first(), last(), headSet(), tailSet() |
| Разрешён null | ✅ Да (один null) | ✅ Да (один null) | ❌ Нет (NullPointerException) |
| Когда использовать | По умолчанию, если порядок не важен | Когда нужен порядок вставки | Когда нужна сортировка |
Интерфейс Set предоставляет несколько базовых методов для работы с элементами:
import java.util.HashSet;
import java.util.Set;
Set<String> fruits = new HashSet<>();
// ➕ Добавление элемента
fruits.add("Яблоко"); // true (добавлено)
fruits.add("Банан"); // true
fruits.add("Яблоко"); // false (уже есть)
// ❌ Удаление элемента
fruits.remove("Банан"); // true (удалено)
fruits.remove("Груша"); // false (не было)
// 🔍 Проверка наличия
System.out.println(fruits.contains("Яблоко")); // true
System.out.println(fruits.contains("Банан")); // false
// 📏 Размер множества
System.out.println(fruits.size()); // 1
// 🗑️ Очистка множества
fruits.clear();
System.out.println(fruits.isEmpty()); // true
Множество можно перебирать тремя способами, как и любую другую коллекцию в Java:
Set<String> fruits = Set.of("Яблоко", "Банан", "Апельсин");
for (String fruit : fruits) {
System.out.println(fruit);
}
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
fruits.forEach(fruit -> System.out.println(fruit));
// Или ещё короче:
fruits.forEach(System.out::println);
Объединение двух множеств — это новое множество, содержащее все элементы из обоих множеств (без дубликатов).
В математике обозначается: A ∪ B
В Java для объединения используется метод addAll():
Set<Integer> setA = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> setB = new HashSet<>(Set.of(3, 4, 5, 6));
// Создаём новое множество для результата
Set<Integer> union = new HashSet<>(setA);
union.addAll(setB);
System.out.println(union); // [1, 2, 3, 4, 5, 6]
// Элементы 3 и 4 встречаются только один раз!
Пересечение двух множеств — это новое множество, содержащее только те элементы, которые есть в обоих множествах одновременно.
В математике обозначается: A ∩ B
В Java для пересечения используется метод retainAll():
Set<Integer> setA = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> setB = new HashSet<>(Set.of(3, 4, 5, 6));
// Создаём копию setA и оставляем только общие элементы
Set<Integer> intersection = new HashSet<>(setA);
intersection.retainAll(setB);
System.out.println(intersection); // [3, 4]
// Только те элементы, которые есть и в A, и в B
Разность двух множеств A и B — это новое множество, содержащее те элементы из A, которых нет в B.
В математике обозначается: A \ B (A минус B)
В Java для разности используется метод removeAll():
Set<Integer> setA = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> setB = new HashSet<>(Set.of(3, 4, 5, 6));
// Создаём копию setA и удаляем из неё элементы setB
Set<Integer> difference = new HashSet<>(setA);
difference.removeAll(setB);
System.out.println(difference); // [1, 2]
// Только те элементы из A, которых нет в B
A \ B ≠ B \ A. В нашем примере: A \ B = {1, 2}, а B \ A = {5, 6}.
Симметрическая разность — это элементы, которые принадлежат либо A, либо B, но не обоим сразу. То есть «уникальные» элементы обоих множеств.
В математике обозначается: A △ B
В Java напрямую такой операции нет, но её можно получить комбинацией:
Set<Integer> setA = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> setB = new HashSet<>(Set.of(3, 4, 5, 6));
// Способ 1: (A ∪ B) \ (A ∩ B)
Set<Integer> union = new HashSet<>(setA);
union.addAll(setB);
Set<Integer> intersection = new HashSet<>(setA);
intersection.retainAll(setB);
union.removeAll(intersection);
System.out.println(union); // [1, 2, 5, 6]
// Способ 2: (A \ B) ∪ (B \ A)
Set<Integer> diff1 = new HashSet<>(setA);
diff1.removeAll(setB);
Set<Integer> diff2 = new HashSet<>(setB);
diff2.removeAll(setA);
diff1.addAll(diff2);
System.out.println(diff1); // [1, 2, 5, 6]
Одна из самых частых задач — убрать повторяющиеся элементы из списка. Множество делает это элегантно:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class RemoveDuplicates {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Анна");
names.add("Борис");
names.add("Анна"); // дубликат
names.add("Виктор");
names.add("Борис"); // дубликат
names.add("Галина");
System.out.println("Было: " + names);
// [Анна, Борис, Анна, Виктор, Борис, Галина]
// Превращаем список в множество (дубликаты исчезают)
Set<String> uniqueNames = new HashSet<>(names);
// Если нужен снова список — превращаем обратно
List<String> uniqueList = new ArrayList<>(uniqueNames);
System.out.println("Стало: " + uniqueList);
// [Анна, Борис, Виктор, Галина] (порядок может отличаться)
}
}
List<String> unique = names.stream().distinct().toList();
Классическая задача социальных сетей — найти общих друзей двух пользователей. Множества решают её в одну операцию:
import java.util.HashSet;
import java.util.Set;
public class CommonFriends {
public static void main(String[] args) {
// Друзья пользователя "Маша"
Set<String> mashaFriends = new HashSet<>();
mashaFriends.add("Аня");
mashaFriends.add("Боря");
mashaFriends.add("Вика");
mashaFriends.add("Дима");
// Друзья пользователя "Петя"
Set<String> petyaFriends = new HashSet<>();
petyaFriends.add("Боря");
petyaFriends.add("Вика");
petyaFriends.add("Гриша");
petyaFriends.add("Дима");
petyaFriends.add("Егор");
// Находим общих друзей (пересечение)
Set<String> commonFriends = new HashSet<>(mashaFriends);
commonFriends.retainAll(petyaFriends);
System.out.println("Общие друзья: " + commonFriends);
// [Боря, Вика, Дима]
System.out.println("Их количество: " + commonFriends.size());
// 3
}
}
Задача: подсчитать количество уникальных слов в тексте. Множество — идеальное решение:
import java.util.HashSet;
import java.util.Set;
public class UniqueWords {
public static void main(String[] args) {
String text = "Мама мыла раму мама мыла окно рама чистая окно чистое";
// Разбиваем текст на слова (по пробелам)
String[] words = text.split(" ");
// Помещаем все слова в множество (приводим к нижнему регистру)
Set<String> uniqueWords = new HashSet<>();
for (String word : words) {
uniqueWords.add(word.toLowerCase());
}
System.out.println("Всего слов: " + words.length); // 10
System.out.println("Уникальных слов: " + uniqueWords.size()); // 6
System.out.println("Слова: " + uniqueWords);
// [мама, мыла, раму, окно, рама, чистая, чистое]
}
}
Часто нужно проверить, все ли элементы множества A содержатся в множестве B. Для этого используется метод containsAll():
Set<Integer> setA = new HashSet<>(Set.of(1, 2));
Set<Integer> setB = new HashSet<>(Set.of(1, 2, 3, 4, 5));
// Является ли A подмножеством B?
boolean isSubset = setB.containsAll(setA);
System.out.println(isSubset); // true
// А наоборот?
boolean isSuperset = setA.containsAll(setB);
System.out.println(isSuperset); // false
A ⊆ B), если каждый элемент A также является элементом B.
Иногда нужно создать множество, которое нельзя изменять после создания. Это полезно для констант и защиты данных от случайных изменений.
Начиная с Java 9, для этого есть удобный метод Set.of():
// Создаём неизменяемое множество
Set<String> colors = Set.of("Красный", "Зелёный", "Синий");
// colors.add("Жёлтый"); // ❌ UnsupportedOperationException!
// colors.remove("Красный"); // ❌ UnsupportedOperationException!
System.out.println(colors); // [Красный, Зелёный, Синий]
System.out.println(colors.size()); // 3
UnsupportedOperationException. Это защита от случайных изменений.
Set<String> mutable = new HashSet<>(colors);
Чтобы хранить в множестве объекты своих классов, нужно правильно реализовать два метода: equals() и hashCode(). Иначе множество будет считать два одинаковых объекта разными.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
// Обязательно переопределяем equals и hashCode!
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Main {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
students.add(new Student("Аня", 20));
students.add(new Student("Аня", 20)); // Дубликат! Не добавится
System.out.println(students.size()); // 1
}
}
removeIf().remove(x) удаляет один элемент, removeAll(set) удаляет все элементы из другого множества.addAll) — все элементы из обоих множеств.retainAll) — только общие элементы.removeAll) — элементы из первого, которых нет во втором.equals() и hashCode().На следующих лекциях мы продолжим изучать структуры данных и коллекции: