сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · Коллекции Java

Вопросы по коллекциям Java на собеседовании

Коллекции спрашивают на каждом собесе Java, и это редкий блок, где ответ проверяется мгновенно: попросят объяснить, что произойдёт с HashMap при плохом hashCode, и картина сразу становится ясной.

91 вопросов в банке·5 подтем·ниже разбор 9

Что спрашивают

Из чего состоит тема

Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.

Разборы подтем

Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.

Примеры вопросов с разбором

  1. #choosing_complexity1 / 9
    Нужен быстрый поиск «есть ли элемент» в большой коллекции. Что выбрать?
    A)HashSet — contains амортизированно O(1)
    B)ArrayList: метод contains проходит по элементам, но для больших списков он ускоряется автоматически
    C)LinkedList: связная структура позволяет проверять наличие элемента быстрее, чем массивовый список
    D)Обычный массив с линейным перебором — для проверки наличия это самый предсказуемый по скорости путь
    показать ответ и разбор
    +A)HashSet — contains амортизированно O(1)

    // разбор: Проверка наличия (contains) в List — линейный перебор O(n). В HashSet — вычисление бакета по хешу и сравнение внутри, амортизированно O(1). На больших объёмах разница огромна. Если нужна и уникальность, и быстрый поиск — HashSet; если ещё и порядок — LinkedHashSet; если сортировка — TreeSet (O(log n)). Список берут, когда важны индексный доступ и дубликаты.

  2. #generics2 / 9
    Зачем нужны дженерики (generics) в Java?
    A)Типобезопасность на компиляции без ручных приведений типов
    B)Чтобы ускорить программу в рантайме: обобщённые коллекции работают быстрее необобщённых за счёт спецкода
    C)Чтобы одна коллекция могла одновременно хранить значения несовместимых типов без ограничений
    D)Чтобы автоматически создавать объекты нужного типа во время выполнения по переданному параметру-классу
    показать ответ и разбор
    +A)Типобезопасность на компиляции без ручных приведений типов

    // разбор: Дженерики дают проверку типов на этапе КОМПИЛЯЦИИ: List<String> не даст положить Integer, и не нужно кастовать при чтении. Это ловит ошибки раньше и делает код читаемее. На производительность они не влияют — существуют только в компиляторе, в рантайме тип стирается (type erasure). Поэтому нельзя, например, создать new T[] или проверить instanceof List<String>.

  3. #iteration_ordering3 / 9
    Что такое fail-fast итератор?
    A)Он бросает ConcurrentModificationException при структурном изменении во время обхода
    B)Итератор, который при ошибке молча пропускает проблемный элемент и продолжает обход дальше без сбоя
    C)Итератор, надёжный потокобезопасный обход коллекции сразу из нескольких потоков без блокировок
    D)Итератор, который заранее копирует всю коллекцию и обходит копию, игнорируя изменения оригинала
    показать ответ и разбор
    +A)Он бросает ConcurrentModificationException при структурном изменении во время обхода

    // разбор: Итераторы обычных коллекций (ArrayList, HashMap) fail-fast: они запоминают счётчик структурных изменений modCount и, обнаружив на очередном шаге, что коллекцию поменяли мимо итератора, бросают ConcurrentModificationException. Это защита от неверного обхода (даже в одном потоке!). Fail-safe итераторы (CopyOnWriteArrayList, ConcurrentHashMap) работают по снимку/без CME, но могут не видеть свежих изменений.

  4. #list_set4 / 9
    Как устроен ArrayList внутри и какова сложность доступа по индексу?
    A)Динамический массив; доступ по индексу — O(1)
    B)Двусвязный список узлов, поэтому доступ к элементу по индексу занимает O(1) за счёт прямой адресации
    C)Хеш-таблица с бакетами: индекс считается как хеш, доступ амортизированно константный, но зависит от коллизий
    D)Сбалансированное дерево, где доступ по индексу требует спуска от корня и стоит O(log n) на каждый элемент
    показать ответ и разбор
    +A)Динамический массив; доступ по индексу — O(1)

    // разбор: ArrayList хранит элементы в массиве с запасом ёмкости, поэтому обращение по индексу — прямая адресация за O(1). Зато вставка/удаление в середину — O(n) (сдвиг хвоста), а при переполнении массив копируется в больший (рост примерно в 1.5 раза). Это структура выбора, когда нужен быстрый доступ по индексу и добавление в конец.

  5. #map_internals5 / 9
    Как HashMap находит значение по ключу в get()?
    A)По hashCode ключа выбирает бакет, внутри сверяет ключи по equals
    B)Линейно перебирает все пары ключ-значение и для каждой вызывает equals, пока не найдёт совпадение
    C)Держит ключи отсортированными и ищет нужный бинарным поиском по естественному порядку за O(log n)
    D)Хранит отдельный индекс-массив адресов и обращается к значению напрямую по номеру вставки ключа
    показать ответ и разбор
    +A)По hashCode ключа выбирает бакет, внутри сверяет ключи по equals

    // разбор: HashMap считает hashCode ключа, приводит его к индексу бакета, а внутри бакета сверяет ключи через equals (в бакете может лежать несколько пар из-за коллизий). При хорошем распределении хешей get/put — амортизированно O(1). Поэтому ключам нужны корректные equals/hashCode, а изменение полей ключа после вставки ломает поиск.

  6. #choosing_complexity6 / 9
    Нужна структура «первым пришёл — первым обработан» (очередь). Что взять?
    A)ArrayList, добавляя в конец и удаляя из начала remove(0) — это и есть эффективная очередь на массиве
    B)ArrayDeque как Queue (offer/poll)
    C)Stack из java.util — он специально спроектирован под очередь и обрабатывает элементы в порядке FIFO
    D)TreeSet, потому что он держит элементы упорядоченными и поэтому естественно выдаёт их по очереди
    показать ответ и разбор
    +B)ArrayDeque как Queue (offer/poll)

    // разбор: ArrayDeque — быстрый дек на кольцевом массиве: O(1) добавление/удаление с обоих концов, поэтому годится и как FIFO-очередь (offer/poll), и как LIFO-стек (push/pop) — и в обеих ролях быстрее legacy-классов Stack и LinkedList. ArrayList под очередь плох: remove(0) сдвигает весь хвост (O(n)). Если нужен приоритет, а не FIFO — PriorityQueue (куча).

  7. #generics7 / 9
    Что такое стирание типов (type erasure)?
    A)Механизм, который в рантайме хранит полную информацию о типе-параметре и позволяет создавать new T()
    B)Дженерик-параметры существуют лишь на компиляции, в рантайме стёрты
    C)Оптимизация, удаляющая неиспользуемые generic-классы из скомпилированного байткода ради размера
    D)Автоматическое приведение всех числовых типов-параметров к double для единообразия вычислений
    показать ответ и разбор
    +B)Дженерик-параметры существуют лишь на компиляции, в рантайме стёрты

    // разбор: Дженерики реализованы стиранием: компилятор проверяет типы и вставляет касты, но в байткоде параметр стирается до его границы (Object или верхней границы). Отсюда ограничения: нельзя new T(), new T[], T.class, instanceof T или List<String> — рантайм этого типа не знает. Плюс — обратная совместимость со старым не-обобщённым кодом. Из-за стирания же есть unchecked-warnings.

  8. #iteration_ordering8 / 9
    Как безопасно удалить элемент во время обхода коллекции?
    A)Вызвать collection.remove(x) прямо в теле for-each — это штатный и поддерживаемый способ удаления
    B)Через iterator.remove() или Collection.removeIf()
    C)Присвоить удаляемому элементу значение null, а затем один раз почистить коллекцию от null после цикла
    D)Обходить коллекцию в двух вложенных циклах, удаляя элемент во внешнем и продолжая во внутреннем
    показать ответ и разбор
    +B)Через iterator.remove() или Collection.removeIf()

    // разбор: Iterator.remove() — единственный корректный способ удалить текущий элемент по ходу обхода через итератор: он синхронизирует внутренний счётчик и не ломает modCount. Ещё чище — removeIf(predicate) на коллекции, который сам обходит и удаляет безопасно. А вот collection.remove(...) в теле for-each меняет коллекцию мимо итератора и роняет CME. Занулять элементы — не удаление.

  9. #list_set9 / 9
    В каком сценарии LinkedList реально выигрывает у ArrayList?
    A)Произвольный доступ по индексу — связному списку не нужно перебирать узлы, он адресует i-й напрямую
    B)Частые вставки/удаления в известной позиции по итератору
    C)Экономия памяти: LinkedList не хранит запасную ёмкость, а узлы занимают меньше места, чем ячейки массива
    D)Перебор большого объёма в цикле: узлы лежат в памяти подряд, поэтому кэш процессора попадает чаще
    показать ответ и разбор
    +B)Частые вставки/удаления в известной позиции по итератору

    // разбор: LinkedList — двусвязный список: добавить/удалить элемент в уже найденной позиции (через ListIterator) — O(1), не сдвигая хвост. Но доступ по индексу — O(n) (перебор от края), памяти больше (два указателя на узел), а разбросанность узлов бьёт по кэшу. На практике ArrayList выигрывает почти всегда; LinkedList оправдан редко — как дек/очередь с интенсивными краевыми операциями.

это 9 из 91

Ещё 82 вопросов по теме — в тренажёре, с движком повторения

Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.

Частые вопросы