Set.add: примеры (JAVA)

Примеры работы метода add в коллекциях
Раздел: Коллекции, Set
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

Пример java
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 безопасен для конкурентного доступа.

Пример java
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 в потоках данных (не рекомендуется: сайд-эффекты)

Пример java
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>>

Пример java
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 для передачи коллекции напрямую, чтобы избежать частых ресайзов.

Пример java
Set s = new HashSet<>(100000);
// затем добавление большого числа элементов
(нет непосредственного вывода - уменьшение затрат на аллокации)

Отслеживание, какие элементы были новыми

Пример java
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]

джава Set.add function comments

En
Set.add Добавляет элемент в набор