Set.add: примеры (JAVA)
Set.add(E e): booleanОбщее описание метода
Метод Set.add(E e) в Java добавляет элемент в коллекцию Set. Подпись метода - boolean add(E e). Поведение определяется контрактом интерфейса Collection: метод возвращает true, если коллекция изменилась в результате вызова (элемент был добавлен), и false, если элемент уже присутствовал и набор не изменился.
Когда используется
- Добавление нового элемента в уникальную коллекцию.
- Построение набора уникальных значений из списка или потока данных.
- Проверка, был ли элемент уже в наборе (через возвращаемое значение).
Возможные аргументы
E e- элемент типа, совместимого с параметром типа набора. Может бытьnullдля реализаций, допускающихnull(например,HashSet), но не для некоторых реализаций с упорядочением или сравнениями, гдеnullприведёт к исключению.
Возвращаемые значения и исключения
- Возвращает
true, если элемент действительно добавлен (набор изменился). - Возвращает
false, если элемент уже присутствовал (по эквивалентности черезequals()иhashCode()). - Может выбросить
NullPointerException, если реализация не поддерживаетnullи переданnull. - Может выбросить
ClassCastException, если тип элемента не совместим с реализацией (например, вTreeSetпри несовместимых сравнениях). - Может выбросить
UnsupportedOperationException, если набор неизменяемый (например, полученный черезSet.of(...)илиCollections.unmodifiableSet(...)).
Особенности реализации
- Уникальность определяется реализацией: в хэш-основанных наборах - по
equals()иhashCode(), в упорядоченных - по сравнениям (ComparableилиComparator). - Потокобезопасность зависит от конкретной реализации:
HashSetне потоко-безопасен,ConcurrentSkipListSetиCopyOnWriteArraySetпредоставляют разные гарантии.
Простые примеры использования
Добавление в HashSet (обычный случай)
import java.util.*;
public class Example1 {
public static void main(String[] args) {
Set s = new HashSet<>();
System.out.println(s.add("a")); // true
System.out.println(s.add("b")); // true
System.out.println(s.add("a")); // false (уже есть)
System.out.println(s);
}
}
true true false [a, b]
Добавление null в HashSet и в TreeSet
import java.util.*;
public class Example2 {
public static void main(String[] args) {
Set h = new HashSet<>();
h.add(null); // допускается
System.out.println(h);
Set t = new TreeSet<>();
t.add(null); // выдаст исключение
}
}
[null]
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.TreeMap.compare(TreeMap.java:...)
...
Поведение с неподходящим типом в TreeSet
import java.util.*;
public class Example3 {
public static void main(String[] args) {
Set set = new TreeSet();
set.add("a");
set.add(1); // ClassCastException при сравнении строк и чисел
}
}
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Comparable
at ...Похожие методы в Java
addAll(Collection<? extends E> c)
Добавляет сразу все элементы из другой коллекции. Возвращает true, если набор изменился. Удобно для массового добавления, но не предоставляет информации по каждому элементу отдельно.
contains(E e) и containsAll(Collection)
Для проверки наличия элемента в наборе. Используется, если нужно только узнать присутствие, без модификации.
remove(Object o)
Удаляет элемент и возвращает true, если он был в наборе. Вместо добавления-замены может использоваться логика удаления.
Особенности выбора
- Для массовых вставок предпочтительнее
addAllпо читаемости и возможной оптимизации. - Если нужна атомарная вставка в многопоточном окружении, выбрать специализированные реализации коллекций (например,
ConcurrentSkipListSet,ConcurrentHashMap.newKeySet()).
Аналоги в других языках
JavaScript
Наборы представлены Set. Метод add возвращает ссылку на сам набор (chainable), а не логический результат.
const s = new Set();
console.log(s.add(1)); // Set {1}
console.log(s.add(1)); // Set {1} - возвращается тот же объект
Set { 1 }
Set { 1 }
Python
Метод add у set возвращает None. Чтобы узнать, был ли добавлен, требуется проверка наличия перед добавлением или использование длины.
s = set()
print(s.add(1)) # None
s.add(1)
print(s) # {1}
None
{1}
C#
HashSet<T>.Add(T item) возвращает bool - поведение близко к Java: true, если элемент добавлен.
var s = new HashSet();
Console.WriteLine(s.Add(1)); // True
Console.WriteLine(s.Add(1)); // False
True False
Go
Нет встроенного типа set; обычно используется map[T]struct{}. Пример: проверка присутствия и установка ключа вручную.
m := make(map[int]struct{})
_, exists := m[1]
if !exists {
m[1] = struct{}{}
fmt.Println("added")
} else {
fmt.Println("already")
}
added
PHP
Нет встроенного простого набора в старых версиях; используют ассоциативные массивы или расширение Ds\Set. Поведение и возвращаемые значения зависят от реализации.
Kotlin
MutableSet.add() аналогично Java: возвращает Boolean.
Lua
Таблицы используются как множества: установка ключа t[x]=true. Возвращаемого значения нет.
Отличия от Java
- В Java возвращается логический результат, как и в C#, в большинстве динамических языков метод добавления не возвращает булево значение или возвращает сам объект (JS).
- Обработка
nullи сравнения зависит от реализации в каждом языке.
Типичные ошибки при использовании
Ожидание добавления дубликата
Частая ошибка - полагать, что add всегда вернёт true. Если элемент с тем же смыслом уже есть (equals/hashCode), результат будет false.
Set s = new HashSet<>();
s.add("x");
boolean r = s.add("x");
System.out.println(r); // false
false
Добавление null в упорядоченные наборы
Set t = new TreeSet<>();
t.add(null); // исключение
Exception in thread "main" java.lang.NullPointerException
at ...
Использование неподходящего типа
Set s = new TreeSet();
s.add("a");
s.add(1); // ClassCastException
Exception in thread "main" java.lang.ClassCastException: ...
Добавление в неизменяемый набор
Set u = Set.of("a", "b");
u.add("c"); // UnsupportedOperationException
Exception in thread "main" java.lang.UnsupportedOperationException
at ...
Проблемы с equals и hashCode
Если у пользовательского класса некорректно реализованы equals() и hashCode(), в хэш-наборе могут появляться дубли или некорректная проверка наличия.
class A { int x; }
// без переопределения equals/hashCode два объекта с одинаковым x будут разными для HashSet
Возможны дубликаты по смыслу при хранении объектов без корректных методов
Изменения и примечания по версиям Java
История и последние изменения
- Метод
addсуществует с момента появления коллекций в Java (Collections Framework, Java 1.2) и остался частью контрактаCollection. - В Java 8 и выше появились другие возможности (Streams, Collectors), меняющие подход к созданию наборов, но контракт
addне изменился. - В Java 9 были добавлены фабричные методы
Set.of(...), которые возвращают неизменяемые наборы; попытка вызватьaddна таких наборах приводит кUnsupportedOperationException. - Появление новых реализаций (например, переделки Concurrent коллекций) даёт дополнительные варианты поведения в многопоточном окружении, но сигнатура
addосталась прежней.
Продвинутые и нестандартные примеры
Пользовательские объекты: корректные equals и hashCode
import java.util.*;
class Person {
String name;
Person(String name){this.name = name;}
@Override public boolean equals(Object o){
return o instanceof Person && Objects.equals(name, ((Person)o).name);
}
@Override public int hashCode(){ return Objects.hash(name); }
}
public class ExAdv1 {
public static void main(String[] args) {
Set s = new HashSet<>();
System.out.println(s.add(new Person("Ivan"))); // true
System.out.println(s.add(new Person("Ivan"))); // false - благодаря equals/hashCode
}
}
true false
Атомарная вставка в многопоточном окружении
Использование ConcurrentHashMap.newKeySet() как concurrent-набора; add безопасен для конкурентного доступа.
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ExAdv2 {
public static void main(String[] args) {
Set cs = ConcurrentHashMap.newKeySet();
System.out.println(cs.add("x")); // true
System.out.println(cs.add("x")); // false
}
}
true false
Использование add в потоках данных (не рекомендуется: сайд-эффекты)
import java.util.*;
import java.util.stream.*;
public class ExAdv3 {
public static void main(String[] args) {
Set s = Collections.synchronizedSet(new HashSet<>());
IntStream.range(0,1000).parallel().forEach(i -> s.add(i));
System.out.println(s.size()); // 1000 - с синхронизацией
}
}
1000
Использование add для группировки значений в Map<K, Set<V>>
import java.util.*;
public class ExAdv4 {
public static void main(String[] args) {
Map> map = new HashMap<>();
addToMap(map, "a", 1);
addToMap(map, "a", 2);
addToMap(map, "b", 2);
System.out.println(map);
}
static void addToMap(Map> m, String k, Integer v){
m.computeIfAbsent(k, key -> new HashSet<>()).add(v);
}
}
{a=[1, 2], b=[2]}
Оптимизация загрузки больших наборов
При массовой загрузке из источника выгодно задавать начальную ёмкость (в случае HashSet) или использовать addAll для передачи коллекции напрямую, чтобы избежать частых ресайзов.
Set s = new HashSet<>(100000);
// затем добавление большого числа элементов
(нет непосредственного вывода - уменьшение затрат на аллокации)
Отслеживание, какие элементы были новыми
List input = List.of("a","b","a","c");
Set set = new HashSet<>();
List newly = input.stream().filter(e -> set.add(e)).collect(Collectors.toList());
System.out.println(newly); // [a, b, c]
[a, b, c]