Java for Beginner
778 subscribers
755 photos
220 videos
12 files
1.28K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Коллекции в Java

Глава 3. Set — множества

Методы add, remove, contains

Методы add, remove и contains — это основные операции для добавления, удаления и проверки элементов в Set. Они наследуются от интерфейса Collection, но в Set имеют специфику из-за уникальности элементов.

add(E e): Добавляет элемент в множество, если его еще нет. Возвращает boolean: true, если добавлен (элемент был уникален), false — если уже существовал (дубликат игнорируется).
remove(Object o): Удаляет элемент, если он существует. Возвращает boolean: true, если удален (элемент был), false — если не найден.
contains(Object o): Проверяет, содержит ли множество элемент. Возвращает boolean: true, если есть, false — если нет.

Эти методы работают за O(1) в HashSet/LinkedHashSet (средний случай) и O(log n) в TreeSet.

Общие нюансы:
Все методы используют equals() для сравнения элементов (и hashCode() в Hash-based реализациях).
Null: Разрешен в HashSet/LinkedHashSet (один), но в TreeSet вызывает NullPointerException при сравнении.
Изменение множества: Методы модифицируют Set in-place (на месте).
Thread-safety: Не гарантирована — используйте synchronized версии для многопоточности.
Generics: Set add(Integer) — ошибка компиляции (типобезопасность).



Метод add(E e): Добавление элементов

add() — основной способ наполнения Set. Если элемент уже есть (по equals()), он не добавляется, и возвращается false.

Поведение в реализациях:
HashSet: Добавляет в хэш-таблицу. Если хэш-коллизия, проверяет equals() в цепочке.
LinkedHashSet: Аналогично HashSet, но обновляет ссылки в списке для порядка вставки (только если добавлен).
TreeSet: Добавляет в дерево, сравнивая через compareTo() или Comparator. Если равен 0 — не добавляет.


Возвращаемое значение: true — добавлен (новый), false — уже был.

Исключения: NullPointerException в TreeSet для null; ClassCastException в TreeSet, если элемент не Comparable.

Нюансы:
Если Set полный (редко, так как resizable), может быть OutOfMemoryError.
Для custom объектов: Без правильного equals()/hashCode() может добавить "дубликаты" (по значению, но не по ссылке).
Модификация объекта после добавления: Не изменяйте поля, влияющие на equals/hashCode (например, в HashSet объект может "потеряться").


Пример кода для add():
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;
import java.util.Set;

public class Main {
public static void main(String[] args) {
// HashSet
Set<String> hashSet = new HashSet<>();
boolean added1 = hashSet.add("Яблоко"); // true
boolean added2 = hashSet.add("Яблоко"); // false (дубликат)
System.out.println(hashSet); // [Яблоко]

// LinkedHashSet
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("Яблоко");
linkedSet.add("Банан");
linkedSet.add("Яблоко"); // false
System.out.println(linkedSet); // [Яблоко, Банан] — порядок вставки

// TreeSet
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(5); // false
System.out.println(treeSet); // [1, 5] — отсортировано
}
}

Вывод показывает уникальность и особенности каждой реализации.



#Java #для_новичков #beginner #Collections #Set #add #remove #contains
👍5
Метод remove(Object o): Удаление элементов

remove() удаляет элемент, если он найден по equals(), и возвращает true/false.

Поведение в реализациях:
HashSet: Находит по хэшу, затем equals() — O(1).
LinkedHashSet: Аналогично, плюс обновляет ссылки списка.
TreeSet: Ищет по compareTo() — O(log n), удаляет узел дерева.


Возвращаемое значение: true — удален, false — не найден.

Исключения: NullPointerException в TreeSet для null; ClassCastException, если тип не совместим.


Нюансы:

Аргумент Object: Можно удалять по объекту любого типа, но сравнивает equals().
После remove: Размер уменьшается, итераторы обновляются.
Для custom: Зависит от equals().

Пример кода для remove():
import java.util.HashSet;
import java.util.Set;

public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Яблоко");
fruits.add("Банан");

boolean removed1 = fruits.remove("Яблоко"); // true
boolean removed2 = fruits.remove("Апельсин"); // false (не найден)

System.out.println(fruits); // [Банан]
}
}

Аналогично для других реализаций: В TreeSet remove сохраняет сортировку.



Метод contains(Object o): Проверка наличия

contains() проверяет, есть ли элемент в Set по equals().

Поведение в реализациях:
HashSet: O(1) — хэш + equals().
LinkedHashSet: O(1), но с overhead списка.
TreeSet: O(log n) — поиск в дереве.


Возвращаемое значение: true — есть, false — нет.

Исключения: Аналогично remove: NPE в TreeSet для null.


Нюансы:
Быстрее, чем в List (O(n)), идеально для проверок уникальности.
Для больших Set: HashSet fastest.


Пример кода для contains():
import java.util.HashSet;
import java.util.Set;

public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Яблоко");

System.out.println(fruits.contains("Яблоко")); // true
System.out.println(fruits.contains("Банан")); // false
}
}



Полезные советы для новичков

add для уникальности: Используйте возвращаемое значение для логики (if (!set.add(e)) { "Дубликат!"; }).
remove/
contains для null: Тестируйте — в HashSet работает, в TreeSet — нет.
Custom объекты: Переопределяйте equals/hashCode (IDE: Generate → equals() and hashCode()).
Эффективность: Для частых
contains — HashSet; для сортировки — TreeSet.
Комбинируйте: Set для фильтра, затем List для порядка.
Ошибки: ClassCastException в TreeSet без Comparable; ConcurrentModification при модификации в цикле (используйте Iterator).



#Java #для_новичков #beginner #Collections #Set #add #remove #contains
👍5
Глава 2. List — списки

Методы remove, contains

Операции remove и contains находятся в тесной концептуальной взаимосвязи — обе требуют поиска элемента в коллекции, но с разными целями и последствиями. В то время как contains выполняет пассивную проверку наличия элемента, remove осуществляет активное извлечение элемента с последующей реструктуризацией коллекции. Это различие в целях приводит к существенным различиям в сложности и поведении операций, особенно в контексте различных реализаций List.


Метод
contains: проверка существования элемента

Метод contains выполняет фундаментальную операцию проверки принадлежности элемента к коллекции. Эта операция основана на семантике равенства, определяемой методом equals() объектов, и требует полного или частичного обхода коллекции в зависимости от ее внутренней структуры.


ArrayList: линейный поиск в массиве

ArrayList, основанный на динамическом массиве, не предоставляет специализированных структур данных для ускорения операций поиска. В результате операция contains требует последовательного обхода всех или части элементов массива.

Детальный процесс выполнения contains(element)

Фаза инициализации поиска:
Операция начинается с анализа входного элемента. Если передан null, поиск будет вестись среди null элементов коллекции. Если передан не-null объект, будет использоваться его метод equals() для сравнения.

Фаза последовательного обхода:
Система инициирует последовательный обход внутреннего массива elementData от индекса 0 до size-1.

Для каждого элемента выполняются следующие шаги:
Извлечение текущего элемента из массива по индексу i
Проверка на null текущего элемента


Сравнение элементов:
Если оба элемента null → совпадение найдено
Если текущий элемент не null и equals(element) возвращает true → совпадение найдено
Иначе → переход к следующему элементу


Фаза завершения поиска:
Как только найдено совпадение, операция немедленно возвращает true. Если обход завершен без нахождения совпадения, возвращается false.

Оптимизации и особенности

Ранний выход:
Основная оптимизация заключается в возможности раннего выхода при нахождении первого совпадения.

Отсутствие структурных изменений:
Операция contains не модифицирует внутреннюю структуру данных и не влияет на счетчик модификаций (modCount).

Влияние порядка элементов:
Время выполнения зависит от позиции искомого элемента в массиве. Элементы в начале находятся быстрее, чем элементы в конце.

Временная сложность
Операция contains в ArrayList имеет временную сложность O(n) в худшем случае, где n — количество элементов в списке. В среднем случае, при равномерном распределении позиций элементов, сложность составляет O(n/2) = O(n).


LinkedList: последовательный обход цепочки узлов

LinkedList, реализованный как двусвязный список, требует полного обхода цепочки узлов для выполнения операции contains, так как не поддерживает прямой доступ к элементам по значению.

Детальный процесс выполнения contains(element)

Фаза инициализации:
Как и в ArrayList, операция начинается с анализа входного элемента и подготовки к использованию метода equals().

Фаза последовательного обхода узлов:
Система начинает обход с головного узла (head) и последовательно перемещается по цепочке:
Извлечение элемента из текущего узла (node.item)
Проверка на null извлеченного элемента
Сравнение элементов с использованием семантики equals()
Переход к следующему узлу через
node.next

Фаза завершения:
Операция возвращает true при первом найденном совпадении или false после полного обхода всех узлов.

Особенности производительности

Отсутствие индексации:
В отличие от ArrayList, LinkedList не может использовать преимущества случайного доступа для ускорения поиска.

Худший случай:
Требуется полный обход всех n узлов, если элемент отсутствует или находится в конце списка.

Лучший случай:
Элемент находится в головном узле, что требует всего одного сравнения.

Временная сложность
Операция contains в LinkedList имеет временную сложность O(n) в худшем и среднем случае, аналогично ArrayList.


#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍1
Сравнительный анализ contains в ArrayList и LinkedList

Количественные характеристики


Время выполнения:
ArrayList: 5-10 наносекунд на сравнение × количество сравнений
LinkedList: 10-20 наносекунд на узел × количество узлов


Потребление памяти:
ArrayList: Минимальное — только локальные переменные
LinkedList: Минимальное — временные переменные обхода


Качественные различия

Локальность памяти:
ArrayList: Отличная — последовательный доступ к непрерывному блоку памяти
LinkedList: Плохая — случайные доступы к узлам в куче


Влияние кэширования:
ArrayList: Высокое — предсказуемый доступ улучшает prefetching
LinkedList: Низкое — непредсказуемые переходы между узлами



Метод remove: удаление элементов

Метод remove выполняет одну из наиболее сложных операций в интерфейсе List — извлечение элемента из коллекции с последующей реструктуризацией внутренней структуры данных. Эта операция существует в двух основных вариантах: удаление по индексу (remove(int index)) и удаление по значению (remove(Object o)).


Удаление по индексу в ArrayList

Удаление элемента из ArrayList требует не только извлечения значения, но и структурной реорганизации — сдвига всех последующих элементов для заполнения образовавшейся пустоты.


Детальный процесс выполнения remove(index)


Фаза валидации:
Происходит проверка корректности индекса — он должен находиться в диапазоне [0, size-1].

Фаза извлечения значения:
Система сохраняет элемент из указанной позиции массива для последующего возврата.

Фаза сдвига элементов:
Начинается критически важный процесс реорганизации массива:
// Концептуальное представление сдвига
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
Этот сдвиг требует копирования всех элементов, находящихся правее удаляемой позиции, на одну позицию влево.


Фаза очистки и обновления:
Последняя позиция массива (elementData[size-1]) устанавливается в null
Размер списка уменьшается (size--)
Счетчик модификаций увеличивается (modCount++)


Возврат значения:
Сохраненный элемент возвращается вызывающему коду.

Стоимость операции

Временная сложность:
Удаление по индексу в ArrayList имеет временную сложность O(n) в худшем случае, где n — количество элементов.

Сложность зависит от позиции удаляемого элемента:
Удаление последнего элемента: O(1)
Удаление первого элемента: O(n)
Средний случай: O(n/2) = O(n)


Потребление памяти:
Операция не требует дополнительного выделения памяти, но может создавать временные объекты при копировании.


Удаление по индексу в LinkedList

Удаление элемента из LinkedList включает два основных этапа: поиск целевого узла и перестройку ссылок соседних узлов.

Детальный процесс выполнения remove(index)

Фаза валидации:
Проверка корректности индекса аналогично ArrayList.

Фаза поиска узла:
В зависимости от положения индекса выбирается стратегия поиска:
Для первой половины списка: обход от головы
Для второй половины: обход от хвоста


Фаза изоляции узла:
После нахождения целевого узла выполняется операция "вырезания" его из цепочки:
Сохранение ссылок: Запоминаются ссылки на предыдущий (prev) и следующий (next) узлы

Обновление связей:
Если есть предыдущий узел, его next ссылка устанавливается на следующий узел
Если есть следующий узел, его prev ссылка устанавливается на предыдущий узел

Коррекция границ:
Если удаляемый узел был головой, голова обновляется на следующий узел
Если удаляемый узел был хвостом, хвост обновляется на предыдущий узел


Фаза очистки и обновления:
Ссылки удаляемого узла обнуляются (item, next, prev) для помощи garbage collector
Размер списка уменьшается
Счетчик модификаций увеличивается


Возврат значения:
Элемент из удаленного узла возвращается вызывающему коду.

Стоимость операции

Временная сложность:
Удаление по индексу в LinkedList имеет сложность O(n) из-за необходимости поиска узла. Однако сам процесс "вырезания" узла выполняется за O(1).

Распределение стоимости:
Поиск узла: O(n)
Перестройка ссылок: O(1)



#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍1
Удаление по значению в ArrayList

Операция remove(Object o) в ArrayList сочетает в себе линейный поиск (аналогично
contains) и структурную реорганизацию (аналогично удалению по индексу).

Детальный процесс выполнения remove(element)

Фаза поиска:
Выполняется последовательный обход массива от начала до конца для поиска первого вхождения элемента. Поиск использует семантику equals() и специально обрабатывает null значения.

Фаза удаления:
Если элемент найден на позиции i:
Сохраняется значение для возврата
Выполняется сдвиг элементов от i+1 до size-1 на одну позицию влево


Последняя позиция обнуляется
Размер уменьшается, modCount увеличивается

Особенности:
Удаляется только первое вхождение элемента
Если элемент не найден, возвращается false
Если элемент найден и удален, возвращается true


Временная сложность

Операция имеет сложность O(n) в худшем случае:
Поиск: O(n) в худшем случае
Удаление: O(n) в худшем случае (при удалении из начала)



Удаление по значению в LinkedList

Процесс аналогичен удалению по индексу, но с предварительным поиском по значению.


Детальный процесс

Фаза поиска:
Последовательный обход узлов от головы до хвоста с использованием equals() для сравнения.

Фаза удаления:
При нахождении узла с совпадающим значением выполняется операция "вырезания" аналогично удалению по индексу.

Особенности:
Удаляется только первый найденный узел с совпадающим значением
Возвращает true при успешном удалении, false при отсутствии элемента


Сравнительный анализ операций удаления

Временная сложность

Удаление по индексу:
ArrayList: O(n) в худшем случае (удаление из начала)
LinkedList: O(n) в худшем случае (удаление из середины)

Удаление по значению:
ArrayList: O(n) поиск + O(n) удаление = O(n)
LinkedList: O(n) поиск + O(1) удаление = O(n)



Потребление памяти

ArrayList:
Не требует дополнительной памяти (кроме временных переменных)
Может оставлять "пустоты" в массиве (обнуленные ссылки)


LinkedList:
Освобождает память узла (24-32 байта на узел)
Требует времени garbage collector для очистки


Многопоточные аспекты

Проблемы конкурентного доступа

Несинхронизированные реализации:
Обе операции не являются thread-safe и могут привести к:
Состояниям гонки при одновременных модификациях
Повреждению внутренних структур данных
Непредсказуемому поведению итераторов


Стратегии синхронизации

Явная синхронизация:
synchronized(list) {
if (list.contains(element)) {
list.remove(element);
}
}


Thread-safe альтернативы:
CopyOnWriteArrayList для сценариев "частое чтение, редкая запись"
Collections.synchronizedList() с external locking
Concurrent коллекции для специализированных use cases


Memory consistency
Операции remove и
contains требуют proper memory barriers для обеспечения visibility изменений между потоками. Без синхронизации нет гарантий, что один поток увидит изменения, сделанные другим потоком.


#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍1
Оптимизации и специализированные сценарии

Эффективные паттерны использования

Для ArrayList:
Удаление с конца более эффективно, чем с начала
Пакетное удаление может быть оптимизировано через создание нового списка
Использование removeAll(Collection) для массовых операций


Для LinkedList:
Удаление из начала/конца значительно эффективнее, чем из середины
Использование removeFirst()/removeLast() для операций на концах
Итераторные методы более эффективны для последовательного удаления


Избегание неэффективных паттернов

Антипаттерны:
Частые удаления из начала ArrayList
Использование
contains перед remove без необходимости (двойной обход)
Игнорирование возможности использования Iterator.remove()


Оптимизированные подходы:
// Вместо:
if (list.contains(element)) {
list.remove(element); // Двойной обход
}

// Использовать:
boolean removed = list.remove(element); // Один обход с ранним выходом


Влияние на итераторы

Fail-fast семантика

Обе операции изменяют modCount, что влияет на поведение итераторов:
Любое изменение списка invalidates все активные итераторы
Последующие операции итератора вызывают ConcurrentModificationException
Только Iterator.remove() может безопасно удалять элементы во время итерации


Безопасное удаление во время итерации

Правильный подход:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (shouldRemove(element)) {
iterator.remove(); // Безопасное удаление
}
}


Неправильный подход:
for (String element : list) {
if (shouldRemove(element)) {
list.remove(element); // ConcurrentModificationException
}
}



Производительность в реальных сценариях

Сравнение операций contains:
ArrayList: 5-100 нс в зависимости от позиции элемента
LinkedList: 10-200 нс в зависимости от позиции и размера списка


Сравнение операций remove:
ArrayList (удаление из конца): 10-20 нс
ArrayList (удаление из начала): 100-5000 нс для 1000 элементов
LinkedList (удаление из конца): 10-30 нс
LinkedList (удаление из начала): 10-30 нс
LinkedList (удаление из середины): 50-5000 нс для 1000 элементов


Влияние размера коллекции

Малые коллекции (до 100 элементов):
Различия между ArrayList и LinkedList минимальны, часто доминируют другие факторы.

Средние коллекции (100-10,000 элементов):
ArrayList обычно превосходит LinkedList для большинства операций, кроме вставки/удаления в начале.

Большие коллекции (более 10,000 элементов):
ArrayList значительно превосходит LinkedList для операций доступа и поиска, но страдает при частых вставках/удалениях в начале.


Практические рекомендации


Критерии выбора реализации

Выбор ArrayList когда:
Преобладают операции случайного доступа и поиска
Редкие вставки/удаления в начале списка
Известен или может быть оценен конечный размер
Критически важна memory locality и кэширование


Выбор LinkedList когда:
Частые вставки/удаления в начале списка
Последовательный доступ является доминирующим паттерном
Размер списка сильно варьируется
Память не является основным ограничением


Оптимизация алгоритмов

Для частых операций contains/remove:
Рассмотреть использование HashSet для операций проверки существования
Использовать специализированные структуры данных для specific use cases
Кэшировать результаты частых проверок


Для пакетных операций:

Использовать removeAll() вместо цикла с индивидуальными удалениями
Рассмотреть создание новых коллекций вместо модификации существующих
Использовать stream API для декларативных операций



#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍2