Множество хранит уникальные значения
HashSet (из java.util) - это коллекция, которая хранит каждое значение не более одного раза. Здесь нет ключей и связанных с ними значений, как в HashMap - просто мешок различных элементов. Его единственная задача - быстро отвечать на один вопрос: «есть ли эта штука здесь?»
Здесь один параметр типа, <ElementType>. Как и с ArrayList и HashMap, переменную обычно типизируют интерфейсом Set, а конструируют HashSet.
add возвращает, было ли значение новым
add не просто сохраняет значение - он возвращает boolean, сообщающий, изменилось ли множество на самом деле. Добавление уже присутствующего значения возвращает false и оставляет множество нетронутым.
Это возвращаемое значение действительно полезно: if (!seen.add(x)) { /* x - повтор */ } позволяет обнаруживать дубликаты в одну строку по ходу дела.
Удаление дубликатов из списка
Поскольку множество отвергает повторы, самый быстрый способ убрать дубликаты из коллекции - сбросить её в множество. Конструктор HashSet принимает любую другую коллекцию:
Это самая частая причина, по которой новички обращаются к множеству. Просто помните, что при таком круговом превращении вы теряете исходный порядок - используйте LinkedHashSet, если порядок важен (рассматривается ниже).
contains, remove и size
Повседневные операции повторяют операции других коллекций:
Главное преимущество перед ArrayList - это contains. Списку приходится обойти каждый элемент, чтобы ответить (O(n)); HashSet переходит к ответу почти напрямую (примерно O(1)). Когда вы замечаете, что вызываете list.contains(...) внутри цикла, обычно это сигнал переключиться на множество.
Операции над множествами: объединение, пересечение, разность
Множества раскрываются, когда вы их комбинируете. Методы читаются почти как обычная речь, как только вы поймёте, какой из них какой:
Ключевая ловушка: addAll, retainAll и removeAll изменяют то множество, на котором вызваны. Именно поэтому в каждом примере a сначала копируется в новый HashSet - иначе вы уничтожили бы оригинал. Создавайте новое множество под каждый результат.
HashSet не сохраняет порядок
Как и HashMap, HashSet не гарантирует никакого порядка обхода, и порядок может различаться от запуска к запуску. Если вам нужна предсказуемость:
LinkedHashSetсохраняет порядок вставки - порядок, в котором вы добавляли элементы.TreeSetдержит элементы отсортированными по естественному порядку (или поComparator, который вы зададите).
Все три реализуют интерфейс Set, так что переключение между ними - это изменение одной строки в конструкторе.
Элементы должны быть хешируемыми
HashSet под капотом опирается на HashMap, поэтому действует то же правило: он находит элементы, вычисляя их хеш, а значит, hashCode() и equals() элемента должны быть согласованы. Встроенные типы вроде String и Integer уже делают это правильно, поэтому дублирующиеся строки "java" выше корректно сливаются. Если вы храните экземпляры собственного класса, переопределите и equals, и hashCode - иначе два объекта, «равные» по смыслу, будут считаться разными, а contains и удаление дубликатов молча перестанут работать.
Далее: Обход коллекций
Теперь вы познакомились с тремя рабочими коллекциями - ArrayList, HashMap и HashSet. Каждую из них обходят немного по-разному, и есть тонкие ловушки (например, изменение коллекции во время цикла по ней). Дальше мы соберём всё воедино и разберём, как аккуратно обходить коллекции с помощью цикла for-each, итераторов и forEach.
Часто задаваемые вопросы
Как создать HashSet в Java?
Объявите его с одним параметром типа - типом элементов - и вызовите конструктор: Set<String> tags = new HashSet<>();. Добавляйте значения через tags.add("java"); и проверяйте вхождение через tags.contains("java");. Импортируйте java.util.HashSet и java.util.Set.
В чём разница между HashSet и ArrayList в Java?
ArrayList хранит каждый добавленный элемент (включая дубликаты) в порядке вставки и индексируется по позиции. HashSet хранит только уникальные значения, не гарантирует никакого порядка, не имеет индекса, а его проверка contains выполняется примерно за константное время, а не обходом всего списка. Выбирайте HashSet, когда важна уникальность или быстрая проверка вхождения, а не позиция.
Как удалить дубликаты из списка в Java?
Передайте список в конструктор HashSet: Set<String> unique = new HashSet<>(list);. Множество автоматически отбрасывает повторяющиеся значения. Если нужно вернуть список (и потеря порядка не важна), оберните его снова: new ArrayList<>(unique). Используйте LinkedHashSet, если хотите сохранить исходный порядок.