Множества: Set
Множество (Set) хранит только уникальные элементы: повторное добавление ничего не меняет. Оно отвечает на вопрос «есть ли такой элемент?» намного быстрее списка.
HashSet
Основная реализация множества — HashSet. Метод add() возвращает true, если элемент добавлен, и false, если такой уже был:
void main() {
Set<String> tags = new HashSet<>();
IO.println(tags.add("java"));
IO.println(tags.add("java"));
IO.println(tags.size());
}Результат:
true
false
1Чтобы понять, что элемент уже есть, множество вызывает hashCode() и equals(). Строки, числа и записи умеют это из коробки, а в своих классах эти методы нужно переопределить — иначе два одинаковых объекта окажутся в множестве дважды.
Проверка наличия
Метод contains() у множества работает почти мгновенно даже на миллионе элементов, тогда как список проверяет элементы по одному. Поэтому множество выбирают, когда нужно часто спрашивать «встречалось ли уже?»:
void main() {
Set<String> banned = Set.of("спам", "реклама");
String word = "спам";
if (banned.contains(word)) {
IO.println("Слово запрещено: " + word);
}
}Результат:
Слово запрещено: спамКак и List.of(), метод Set.of() создаёт неизменяемое множество.
Порядок элементов
HashSet не хранит порядок добавления: элементы лежат так, как удобно для быстрого поиска. Если порядок важен, есть две другие реализации. LinkedHashSet помнит порядок добавления, а TreeSet хранит элементы отсортированными:
void main() {
Set<String> linked = new LinkedHashSet<>();
Set<String> sorted = new TreeSet<>();
for (String city : List.of("Рим", "Берлин", "Осло", "Берлин")) {
linked.add(city);
sorted.add(city);
}
IO.println(linked);
IO.println(sorted);
}Результат:
[Рим, Берлин, Осло]
[Берлин, Осло, Рим]Убрать повторы из списка
Частый приём: передать список в конструктор множества — дубликаты исчезнут сами. LinkedHashSet при этом сохранит исходный порядок:
void main() {
List<Integer> visits = List.of(3, 1, 3, 2, 1);
List<Integer> unique = new ArrayList<>(new LinkedHashSet<>(visits));
IO.println(unique);
}Результат:
[3, 1, 2]Операции над множествами
Методы addAll(), retainAll() и removeAll() дают объединение, пересечение и разность. Они меняют множество, у которого вызваны, поэтому сначала делают копию:
void main() {
Set<String> anna = Set.of("кино", "книги", "бег");
Set<String> oleg = Set.of("бег", "шахматы", "кино");
Set<String> common = new TreeSet<>(anna);
common.retainAll(oleg);
IO.println("Общие: " + common);
Set<String> onlyAnna = new TreeSet<>(anna);
onlyAnna.removeAll(oleg);
IO.println("Только у Анны: " + onlyAnna);
}Результат:
Общие: [бег, кино]
Только у Анны: [книги]Частая ошибка
Set.of() не принимает повторяющиеся значения: вместо того чтобы молча убрать дубликат, он выбрасывает исключение:
void main() {
Set<String> colors = Set.of("red", "green", "red");
}Результат:
Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: redЕсли данные могут повторяться, создавайте множество через new HashSet<>(List.of(...)).