Коллекции: вопросы с ответами
73 разобранных вопросов по теме «Коллекции». Каждый — с правильным ответом и пояснением.
- В чём разница: ArrayList vs LinkedList?
ArrayList: O(1) доступ по индексу, O(n) вставка в середину, дружелюбно к CPU-кэшу (непрерывная память). LinkedList: O(1) вставка в начало/конец (если есть ссылка), O(n) доступ по индексу, так как нужно последовательно идти по узлам двусвязного списка.
- ArrayList vs LinkedList — где быстрее вставка в середину?
У LinkedList O(1) на саму вставку, но O(n) на поиск позиции. В реальности ArrayList почти всегда быстрее из-за локальности в кэше CPU.
- ArrayList vs LinkedList — что когда использовать?
ArrayList: быстрый доступ по индексу O(1), быстрая итерация. LinkedList: быстрая вставка/удаление в начале O(1), но поиск O(n). На практике почти всегда выигрывает ArrayList.
- В чём разница: checked vs unchecked исключения?
Checked: от Exception (не RuntimeException) — компилятор требует throws/catch. Примеры: IOException, SQLException. Unchecked: от RuntimeException и Error — обрабатывать не обязательно. Примеры: NullPointerException, IllegalArgumentException.
- В чём разница: ConcurrentHashMap: Java 7 vs Java 8?
Java 7: массив Segment (ReentrantLock), каждый сегмент — свой HashMap. Java 8: убрали Segment, CAS + synchronized на головах бакетов (лучше параллелизм). Treeify при 8 коллизиях.
- Что такое ConcurrentHashMap Java 8?
CAS + synchronized на головах бакетов. Treeify при 8 коллизиях. null запрещён. computeIfAbsent атомарен.
- CopyOnWriteArrayList: плюсы и минусы?
Плюсы: lock-free чтение (читатели не блокируются), fail-safe итератор (работает со snapshot). Минусы: при каждой записи — копирование всего массива → дорого. Подходит: много чтений, редкие записи (listeners, подписчики).
- В чём разница: Fail-fast vs fail-safe итераторы?
Fail-fast (HashMap, ArrayList) кидают ConcurrentModificationException при изменении коллекции. Fail-safe (ConcurrentHashMap, CopyOnWriteArrayList) работают со снимком.
- Что такое Fail-fast итератор?
ConcurrentModificationException при изменении коллекции не через сам итератор. Реализован через сравнение modCount с expectedModCount. Не гарантирован в многопоточной среде — только best effort. Альтернатива: CopyOnWriteArrayList (fail-safe).
- В чём разница: HashMap vs ConcurrentHashMap?
HashMap: не потокобезопасен, допускает null-ключ. ConcurrentHashMap: Java 7 — Segment-блокировки, Java 8+ — CAS + synchronized на головах бакетов. null запрещён. computeIfAbsent — атомарная операция.
- В чём разница: HashMap vs ConcurrentHashMap — ключевые различия?
HashMap не потокобезопасен, допускает null-ключ/значение. ConcurrentHashMap потокобезопасен, не допускает null, в Java 7 использовал сегментную блокировку, в Java 8+ — CAS + synchronized на головах бакетов.
- В чём разница: HashMap vs Hashtable vs LinkedHashMap vs WeakHashMap?
HashMap: не потокобезопасен, допускает null-ключ. Hashtable: потокобезопасен (synchronized на всё), устаревший, нет null. LinkedHashMap: порядок вставки (или access order для LRU). WeakHashMap: держит ключи через слабые ссылки, записи удаляются GC, когда на ключ нет сильных ссылок.
- В чём разница: HashMap vs TreeMap vs LinkedHashMap?
HashMap — хэш, без порядка, O(1). TreeMap — красно-чёрное дерево, отсортирован по ключам, O(log n). LinkedHashMap — сохраняет порядок вставки или access order.
- HashMap vs TreeMap vs LinkedHashMap — когда что?
HashMap — быстрый доступ без порядка. TreeMap — сортировка ключей, O(log n). LinkedHashMap — сохраняет порядок вставки или access order.
- HashMap: расчёт бакета на примере?
Формула: (n-1) & hash(key). Если capacity=16 (n=16): 15 & hash. 15 в бинарном = 1111. hash = 2_000_000_000: 2000000000 & 15 = 0 (последние 4 бита = 0000). Результат: bucket 0. Поэтому capacity — степень двойки: побитовое И эквивалентно взятию модуля.
- HashMap — устройство?
Node<K,V>[]. Размер — степень двойки (default 16). Индекс: (n-1) & hash(key). Коллизии — список. Java 8+: TREEIFY_THRESHOLD=8 И capacity ≥ 64 → red-black tree.
- HashSet — на основе чего реализован?
На HashMap, где значение — заглушка (PRESENT). Все операции делегируются HashMap.
- В чём разница: List.of(1,2,3) vs new ArrayList?
List.of — immutable, add/remove → UnsupportedOperationException, null-элементы запрещены. ArrayList — обычный mutable список.
- List.of(1,2,3) vs new ArrayList<>() — в чём разница?
List.of возвращает неизменяемый список. add/remove бросают UnsupportedOperationException.
- Что такое load factor и resize?
Порог 0.75 по умолчанию. При size >= capacity * loadFactor — resize: массив вдвое + перехеширование ВСЕХ элементов (дорого!). Совет: если знаешь количество элементов — задай initialCapacity = expectedSize / 0.75 + 1.
- Sequenced Collections (Java 21) — что это?
Новый интерфейс с методами getFirst, getLast, addFirst, addLast, reversed. Унифицирует работу с упорядоченными коллекциями.
- В чём разница: TreeMap vs LinkedHashMap?
TreeMap: красно-чёрное дерево, O(log n), ключи отсортированы (Comparable/Comparator). LinkedHashMap: HashMap + двусвязный список — порядок вставки. accessOrder=true — LRU-кэш. removeEldestEntry() для ограничения размера.
- В чём разница между List.of(1,2,3) и new ArrayList<>()?
List.of возвращает immutable-список. Любая попытка изменить — UnsupportedOperationException. И в нём нельзя null.
- Зачем переопределять hashCode и equals для ключа HashMap?
HashMap использует hashCode для бакета и equals для разрешения коллизий. Если по умолчанию (Object) — каждый объект уникален, два «равных по смыслу» User'а с одинаковым id дадут разные hashCode, попадут в разные бакеты и не найдутся. Поэтому нужно переопределить оба метода согласованно, чтобы поиск по логически равному ключу работал.
- Что такое Иерархия исключений в Java?
Throwable → Error (системные, не ловим) и Exception. От Exception → checked и RuntimeException (unchecked).
- Как обработать исключение в REST-контроллере?
@ExceptionHandler в самом контроллере или глобально через @RestControllerAdvice + @ExceptionHandler.
- Как обработать исключение глобально?
@RestControllerAdvice + @ExceptionHandler. Внутри класса — просто @ExceptionHandler на методе контроллера.
- Как работает put / get в HashMap?
put: вычислили hashCode ключа → перемешали его биты и по & (n-1) определили бакет → в бакете ищем по equals существующий ключ. Если нашли — обновляем значение. Если нет — добавляем новый Node в цепочку (при переполнении бакета — в дерево). get работает симметрично: бакет по hashCode, ключ по equals.
- Как работает resize в ArrayList?
Новый массив в 1.5 раза больше, копирование через Arrays.copyOf.
- Как растёт ArrayList?
Начальная capacity = 10 (массив создаётся лениво при первом add). При переполнении создаётся новый массив размером примерно 1.5× от старого (oldCapacity + oldCapacity >> 1), элементы копируются. Метод trimToSize() ужимает массив до текущего количества элементов.
- Как устроен ArrayList?
Динамический массив. При нехватке места создаёт новый массив в 1.5 раза больше и копирует через Arrays.copyOf.
- Как устроен ArrayList внутри?
Динамический массив. При нехватке места создаёт новый массив в 1.5 раза больше и копирует данные через Arrays.copyOf.
- Как устроен HashMap?
Массив Node<K,V>[]. Размер — степень двойки (default 16). Индекс: (n-1) & hash(key), hash — XOR верхних 16 бит с нижними. Коллизии — связный список (chaining). Java 8+: при TREEIFY_THRESHOLD=8 И capacity ≥ 64 → red-black tree.
- Как устроен HashMap? Что такое bucket, как разрешаются коллизии?
Массив связных списков (Node[]). Бакет = ячейка массива. Коллизия → цепочка.
- Как устроена HashMap внутри?
Массив бакетов (table). По хэшу ключа определяется индекс бакета ((n-1) & hash при capacity — степени двойки). В бакете — связный список узлов Node (hash + key + value + next); при длине ≥ 8 и достаточной capacity список превращается в красно-чёрное дерево.
- Какая сложность операций в HashMap?
Среднее: get / put / containsKey — O(1). Худший случай при плохих хэшах: O(log n) при treeified бакете, O(n) если treeify ещё не сработал.
- Какая сложность у HashMap.get?
O(1) в среднем. O(log n) худший случай при treeified бакете (с Java 8). O(n) при катастрофически плохом hashCode.
- Какие основные интерфейсы в Collection Framework?
Collection (List, Set, Queue), Map (отдельно). List — упорядоченный с дубликатами. Set — без дубликатов. Map — пары ключ-значение.
- Какие реализации Set?
HashSet — на основе HashMap (значение в виде специальной заглушки). Не сохраняет порядок. LinkedHashSet — HashSet + порядок вставки. TreeSet — на основе TreeMap (красно-чёрное дерево), элементы отсортированы, операции O(log n).
- Какое исключение лучше — выбросить своё или использовать стандартное?
Если ситуация уникальна для домена — своё (наследник RuntimeException обычно). Если стандартное (например, IllegalArgumentException) подходит — лучше его.
- В чём разница: Коллекции: ArrayList vs LinkedList vs HashMap?
ArrayList: динамический массив, O(1) доступ, O(n) вставка в середину. LinkedList: двусвязный список, O(1) вставка/удаление у известного узла, O(n) доступ. HashMap: бакеты + связный список (метод цепочек) → дерево при ≥8 коллизиях.
- Можно ли byte[] как ключ HashMap?
Технически можно, но hashCode у массива — по адресу (identityHashCode), а equals — по ссылке. Два одинаковых по содержимому массива дадут разные хэши. Нужно обернуть в ByteBuffer или свой класс с переопределёнными equals/hashCode.
- Можно ли null в HashMap?
HashMap — да, ОДИН null-ключ (лежит в table[0]), null-значений сколько угодно. TreeMap — null-ключ нельзя (бросит NPE при сравнении). Hashtable — null нельзя ни в ключе, ни в значении (NPE).
- Можно ли null-ключ в HashMap?
Да, один null-ключ, кладётся в bucket 0. В ConcurrentHashMap нельзя ни ключ, ни значение.
- Можно ли в HashMap положить null-ключ или null-значение?
В HashMap — да (один null-ключ хранится в bucket 0). В ConcurrentHashMap — нельзя ни ключ, ни значение.
- Можно ли в одном catch ловить несколько типов исключений?
Да, multi-catch с Java 7: catch (IOException | SQLException e). Переменная при этом effectively final.
- Можно ли изменить первичный ключ?
Технически — да, через ALTER. Но фактически — нельзя, потому что: (1) на нём FK из других таблиц. (2) Индексы строятся по PK. (3) В кластерных индексах данные физически отсортированы по PK. Операция дорогая и опасная — требует перестройки индексов и обновления связей.
- Можно ли использовать как ключ изменяемый объект (например, массив)?
Технически можно, но опасно: если изменить объект после добавления — его hashCode может измениться, и найти элемент станет невозможно.
- На какие исключения откатывается транзакция по умолчанию?
На RuntimeException и Error. Checked-исключения НЕ откатывают по умолчанию — нужно rollbackFor = Exception.class.
- Что такое Назови несколько unchecked исключений?
NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ClassCastException, NumberFormatException.
- Что такое Обработка исключений?
@RestControllerAdvice + @ExceptionHandler. ErrorDto с code, message, timestamp. HTTP-статусы: 400 (валидация), 404 (не найдено), 409 (бизнес-конфликт), 500 (непредвиденные). MethodArgumentNotValidException для ошибок @Valid отдаёт 400.
- Что такое Основные интерфейсы Collection Framework?
Collection (List, Set, Queue) и Map (отдельно, НЕ наследует Collection). List — упорядоченный с дубликатами. Set — без дубликатов. Queue — очередь. Map — пары ключ-значение. Реализации: ArrayList, LinkedList, HashSet, HashMap.
- Почему ArrayList на практике быстрее LinkedList?
Cache locality. ArrayList хранит элементы в непрерывном куске памяти — процессор подгружает целые блоки в L1/L2 кэш. У LinkedList узлы разбросаны по куче — каждый next() это cache miss, что и замедляет обход на практике.
- Почему Map отдельно от Collection?
Collection хранит элементы — единичные значения. Map хранит ПАРЫ ключ-значение, у неё другой API: put(key, value), get(key), entrySet(). Хотя по сути обе — структуры данных.
- Почему ключ HashMap должен быть иммутабельным?
Если изменить поле, участвующее в hashCode — объект окажется в «неправильном» bucket. contains() вернёт false. Объект потерян — ни найти, ни удалить. Утечка памяти. Решение: immutable-ключи (String, Integer, record).
- Что такое Разница между checked и unchecked исключениями?
Checked наследуются от Exception (но не RuntimeException) — компилятор требует обработки или throws. Unchecked — от RuntimeException и Error — обрабатывать необязательно.
- Что такое Разница между map и flatMap?
map: T → R. flatMap: T → Stream с уплощением результата.
- Что такое Расскажи иерархию исключений?
Throwable — корень. Делится на Error (OOM, StackOverflow — не ловить) и Exception. Exception → checked (IOException, SQLException — обязаны быть в throws) и RuntimeException → unchecked (NullPointerException и потомки, которые не требуют объявления в throws).
- Что такое Расскажи про реализации Map?
HashMap — основная, не потокобезопасна, без порядка, null-ключ можно. LinkedHashMap — порядок вставки или access-order (последнее нужно для LRU-кэша). TreeMap — отсортирована по ключу, реализует NavigableMap (firstKey, floorKey и т.д.), операции за O(log n).
- Расскажите про контракт equals/hashCode. Что случится с HashMap при константном hashCode?
Если a.equals(b), то hashCode одинаков. Обратное необязательно. При константном hashCode — все в одном bucket: Java 7 — список O(n), Java 8+ при 8 коллизиях — red-black tree O(log n). Деградация производительности поиска с O(1) до O(n) или O(log n).
- Что такое Сложность операций HashMap?
put, get, remove — O(1) в среднем. В худшем случае O(log n) благодаря дереву (Java 8+).
- Сложность операций HashMap: put, get, remove?
Амортизированная O(1), в худшем случае O(log n) (Java 8+) или O(n) (Java 7).
- Что такое Состояния потока?
NEW (создан), RUNNABLE (готов или выполняется), BLOCKED (ждёт монитор), WAITING / TIMED_WAITING (ждёт сигнал/время), TERMINATED.
- Что такое Сравни ArrayList и LinkedList по сложности?
Доступ по индексу: ArrayList O(1), LinkedList O(n) (нужно идти от головы или хвоста). Вставка в конец: ArrayList амортизированный O(1), LinkedList O(1). Вставка в середину: ArrayList O(n) (сдвиг элементов), LinkedList O(1), только если итератор уже стоит на нужном узле (иначе поиск позиции O(n)).
- Чем HashSet отличается от LinkedHashSet?
HashSet — без гарантии порядка. LinkedHashSet хранит порядок добавления. Дополнительная память на двусвязный список, но итерация предсказуемая. Часто используют как «уникальные значения с сохранением порядка».
- Чем Iterator отличается от ListIterator?
Iterator — однонаправленный обход. Методы hasNext, next, remove. Доступен для любой Collection. ListIterator — только у List. Двунаправленный (hasPrevious / previous), умеет add/set и знает индексы (nextIndex / previousIndex).
- Чем отличается List от ArrayList?
List — интерфейс, ArrayList — реализация. Правило: писать переменные через интерфейс — List<String> list = new ArrayList<>(); — чтобы потом можно было сменить реализацию без правок остального кода.
- Что будет, если использовать как ключ изменяемый объект и потом его изменить?
Объект «потеряется». hashCode станет другим, и при поиске мы попадём не в тот bucket.
- Что поменялось в HashMap с Java 8?
При 8 элементах в бакете и размере таблицы ≥ 64 список превращается в red-black tree. Поиск становится O(log n) вместо O(n) в худшем случае.
- Что произойдёт, если положить объект в HashSet, а потом изменить поле, участвующее в hashCode?
Потеряешь объект — найти его через contains() будет уже нельзя. Классическая ошибка с mutable-объектами в коллекциях.
- Что такое checked и unchecked исключения?
Checked (наследники Exception, кроме RuntimeException) — компилятор требует обработать (catch) или объявить в throws; применяются для ожидаемых внешних ошибок (IO, БД). Unchecked (RuntimeException и его наследники, а также Error) обрабатывать не обязательно.
- Что такое fail-fast итератор?
Итератор, который кидает ConcurrentModificationException, если коллекция изменена не через сам итератор. Так работают итераторы у HashMap, ArrayList.
- Что такое load factor?
Пороговое соотношение size / capacity (по умолчанию 0.75), при превышении которого таблица расширяется. То есть когда size превышает 75% от capacity, capacity увеличивается в 2 раза и все элементы перехэшируются под новый размер.