← Все публикации

☕ Java на собеседовании: вопросы и задачи для проверки понимания

Interview Ninja

Можно выучить, что HashMap хранит пары ключ-значение, и растеряться, когда спросят: почему объект, который мы только что положили в карту, больше не находится? Здесь уже мало назвать коллекцию. Нужно понять, какие данные участвуют в поиске и что изменилось.

На Java-собеседовании могут попросить объяснить устройство языка, решить задачу, разобрать чужой код или спроектировать часть сервиса. Содержание зависит от вакансии и уровня. В этой статье разберём основные механизмы и потренируемся объяснять ход мыслей, а не угадывать нужное определение. Фрагменты методов и выражений предполагают включение в класс и необходимые импорты.

Разработчик исследует устройство механизма и связи между его частями.

Иллюстрация подготовки к Java-собеседованию.

Java, большие системы и требования к кандидату

Java используется в крупных долгоживущих системах. Например, команды Яндекс 360 указывают Java в стеке нескольких сервисов и описывают как технические, так и продуктовые задачи. В такой среде полезно понимать не только то, как написать метод, но и как он поведёт себя при изменении данных, сбое или одновременных запросах.

При этом Java не ограничена корпорациями, а размер компании сам по себе не определяет формат интервью. Кандидата могут спросить о внутреннем устройстве коллекций, попросить решить алгоритмическую задачу или сделать работающий сервис. Например, автор разбора задач для начинающих Java-разработчиков среди возможных заданий описывает и Spring-приложение по спецификации. Это пример конкретного опыта, а не статистика всех собеседований.

Практический вывод: готовьтесь и объяснять механизмы, и писать код. Глубину JVM, архитектуры и Spring уточняйте по требованиям конкретной роли. Начинающему важно понимать основы и уметь рассуждать о небольшом сервисе; строить распределённую платформу на каждом junior-интервью не требуется.

С чего начать подготовку

Разделите темы на несколько групп:

  1. Java Core: типы, ссылки, методы, ООП и исключения.
  2. Коллекции и алгоритмы: List, Set, Map, выбор структуры данных, оценка времени и памяти.
  3. Многопоточность: что происходит при общем изменяемом состоянии и зачем нужна синхронизация.
  4. Работа сервиса: HTTP, база данных, обработка ошибок и границы ответственности компонентов.
  5. Spring и другие инструменты: по требованиям вакансии. Spring не является частью языка Java.

Дальше проверьте себя на вопросах и задачах. Для каждого ответа попробуйте назвать не только правило, но и пример, где оно влияет на поведение программы.

Вопрос 1. Чем отличаются JDK, JVM и Java Core?

Короткий ответ: Java Core обычно называют основные возможности языка и стандартной библиотеки, без привязки к конкретному фреймворку. JDK содержит инструменты для разработки и запуска Java-программ, в том числе компилятор javac. JVM загружает и исполняет байт-код.

Обычно компилятор преобразует исходный код .java в байт-код .class. JVM выполняет его на конкретной платформе; горячие участки могут компилироваться в машинный код во время работы программы.

Что может проверять интервьюер: понимаете ли вы путь от исходного текста до выполнения, а не только расшифровку аббревиатур.

Вопрос 2. Как Java передаёт аргументы в метод?

Короткий ответ: по значению. Для объекта копируется значение ссылки: обе ссылки указывают на один объект. Через полученную ссылку метод может изменить объект, но переприсваивание параметра не изменит переменную вызывающего кода.

static void change(StringBuilder text) {
    text.append("!");
    text = new StringBuilder("другая строка");
}

StringBuilder message = new StringBuilder("Привет");
change(message);
System.out.println(message); // Привет!

append изменил исходный объект. Затем параметру text присвоили ссылку на другой объект, но переменная message по-прежнему указывает на первый.

Как объяснить ход мысли: сначала назовите, какое значение скопировано, затем отдельно проследите изменение объекта и переприсваивание переменной. Это помогает не запутаться в выражении "передали объект по ссылке".

Вопрос 3. Чем отличаются == и equals?

Для примитивных значений == сравнивает значения. Для ссылок он проверяет, ссылаются ли они на один объект. Метод equals задаёт равенство объектов по правилам конкретного класса: например, у String сравнивается содержимое. Унаследованный от Object вариант по умолчанию проверяет идентичность объекта.

String first = new String("Java");
String second = new String("Java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true

Что может проверять интервьюер: различаете ли вы две отдельные вещи: один и тот же объект и два объекта с равным содержимым. Если ссылка может быть null, вызов first.equals(...) потребует отдельной обработки.

Вопрос 4. Как связаны equals и hashCode?

Правило контракта Object: если два объекта равны по equals, их hashCode должен совпадать. Обратное неверно: одинаковый хеш ещё не означает равенство. Такое совпадение может быть коллизией.

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

Задача: почему не находится ключ?

Вы положили объект в HashMap, а затем изменили поле, которое его equals и hashCode используют для сравнения. После этого поиск того же объекта может не найти запись: например, если хеш при поиске отличается от хеша, с которым запись была размещена.

Как рассуждать:

  1. Какие поля определяют равенство ключа?
  2. Могли ли они измениться, пока ключ находится в карте?
  3. Согласованы ли equals и hashCode?
  4. Можно ли использовать стабильный идентификатор или неизменяемый ключ?

Не обязательно, чтобы весь объект был неизменяемым: важно не менять значимые для равенства поля, пока он используется как ключ. record тоже не делает автоматически неизменяемым содержимое вложенного изменяемого объекта.

Что может проверяться: понимание контракта и способность связать его с ошибкой в работающей системе, а не знание слова "корзина".

Вопрос 5. Что важно знать о HashMap?

  • HashMap не гарантирует порядок элементов. Если результат должен сохранять порядок, это нужно учесть при выборе структуры.
  • Она допускает ключ null и значения null. Если map.get(key) вернул null, это может означать как отсутствие ключа, так и сохранённое значение null. Для различения используют containsKey.
  • Она не синхронизирована. При совместном доступе потоков с изменениями нужна подходящая стратегия синхронизации или другая структура.
  • По документации Java 21, базовые get и put имеют константную оценку при хорошем распределении хешей. Это условие, а не обещание одинаковой скорости для любых данных.

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

Вопрос 6. Чем отличаются проверяемые и непроверяемые исключения?

Для checked-исключения, например IOException, компилятор требует обработать его или объявить в throws. Unchecked-исключения, наследники RuntimeException, такого требования не имеют. Отдельная ветвь Error описывает другие серьёзные проблемы, а не обычный механизм обработки ожидаемых ошибок приложения.

Возможный ответ на уточнение: если приложение не может прочитать файл, нужно решить, может ли текущий слой восстановиться. Если нет, ошибку можно передать выше, сохранив причину; на границе приложения важно не раскрывать внутренние детали пользователю и оставить полезную информацию для диагностики.

Что может проверяться: не только знание иерархии, но и понимание, где ошибка действительно может быть обработана. Тип исключения сам по себе не решает архитектуру приложения.

Вопрос 7. Что не так со счётчиком в нескольких потоках?

count++;

Если несколько потоков одновременно увеличивают общую переменную, операция может потерять обновления: она состоит не только из одного логического действия "прибавить единицу". Одного volatile для атомарности count++ недостаточно: он отвечает за видимость и определённые гарантии порядка, но не превращает составное увеличение в атомарную операцию.

В зависимости от задачи можно обсудить синхронизацию или AtomicInteger. Перед выбором уточните, что именно разделяют потоки и какие ещё действия должны выполняться вместе со счётчиком.

Что может проверяться: замечаете ли вы общее изменяемое состояние и различаете ли видимость изменений и атомарность операции. Сложные модели синхронизации стоит изучать глубже, если этого требует роль.

Задача 1. Подсчитать повторения слов

Условие: дан список слов. Верните количество каждого слова. В этом примере считаем, что список не равен null, а каждое слово непустое и уже приведено к нужному регистру. Порядок вывода не важен.

import java.util.HashMap;
import java.util.List;
import java.util.Map;

static Map<String, Integer> countWords(List<String> words) {
    Map<String, Integer> counts = new HashMap<>();
    for (String word : words) {
        counts.put(word, counts.getOrDefault(word, 0) + 1);
    }
    return counts;
}

Для List.of("java", "sql", "java") результат содержит java=2 и sql=1; порядок пар не определён. Для пустого списка результат пустой.

Ход мысли: карта связывает слово со счётчиком. Для каждого элемента берём предыдущее значение или ноль и увеличиваем его. Если нужно считать Java и java одинаковыми, это отдельное требование к нормализации, которое стоит уточнить до решения.

При n словах время в среднем O(n) при хорошем распределении хешей; дополнительная память O(k), где k обозначает число различных слов. Это оценка для структуры данных, а не гарантия одинаковой стоимости обработки строк любой длины.

Задача 2. Почему null не всегда означает отсутствие?

Map<String, Integer> scores = new HashMap<>();
scores.put("Анна", null);

System.out.println(scores.get("Анна"));         // null
System.out.println(scores.get("Борис"));        // null
System.out.println(scores.containsKey("Анна")); // true
System.out.println(scores.containsKey("Борис")); // false

В первом случае ключ есть, но ему соответствует null. Во втором ключа нет. Это разные состояния, хотя один get возвращает одинаковое значение.

Уточните условие: допустимо ли null как значение? Если нет, возможно, лучше явно запретить его в контракте. Если допустимо, используйте проверку наличия ключа там, где различие важно.

Что может проверяться: умеете ли вы находить неоднозначность в данных и задавать вопрос о контракте, прежде чем писать исправление.

Небольшая архитектурная задача: создание заказа

Представьте, что нужно принять запрос на создание заказа и сохранить его. Не начинайте сразу с микросервисов. Сначала уточните: какие данные обязательны, нужно ли резервировать товар, что считать успешным созданием и может ли клиент повторить запрос после таймаута.

Если в вакансии отдельно требуется работа с базой данных, можно также разобрать вопросы и задачи по SQL для собеседования.

Один из возможных вариантов разделения:

HTTP-запрос → контроллер → сервис → доступ к данным → база
  • Контроллер принимает запрос и преобразует его в удобный для приложения вид.
  • Сервис реализует правила создания заказа и координирует необходимые действия.
  • Компонент доступа к данным сохраняет и читает сведения из хранилища.

Это полезная схема для обсуждения, а не единственная обязательная архитектура. Теперь представим сбой: клиент не получил ответ. Успела ли база сохранить заказ? По одному таймауту это может быть неизвестно. Если клиент повторит запрос, возможен дубликат.

Что обсудить: какие действия должны быть атомарны в одной базе, как распознать повторный запрос и какие ограничения на уникальность помогут избежать дублей. Если оплата выполняется внешним сервисом, обычная транзакция одной базы сама по себе не откатит уже выполненный внешний платёж: его обработку нужно проектировать отдельно.

Начинающему достаточно последовательно разобрать требования и возможные сбои; более опытного кандидата могут попросить глубже обосновать выбранные гарантии и компромиссы. Конкретные ожидания зависят от вакансии.

Spring и внедрение зависимостей: отдельно от Java Core

Spring является фреймворком, а не частью языка Java. Он, в частности, помогает управлять компонентами приложения. Внедрение зависимости означает, что объект получает нужный ему компонент извне, вместо того чтобы обязательно создавать конкретную реализацию внутри себя.

Например, сервису можно передать в конструктор объект, который сохраняет заказы. Тогда в тесте проще подставить другую реализацию и проверить логику сервиса отдельно от реальной базы. Такой приём возможен и без Spring; контейнер фреймворка может управлять созданием и передачей компонентов.

Если в вакансии указан Spring, полезно понимать не только названия аннотаций, но и то, какие зависимости нужны приложению и где обрабатывается запрос. Официальное руководство Spring показывает создание небольшого REST-сервиса целиком. Такая практика дополняет понимание механизмов, а не противоречит ему.

Как тренироваться перед интервью

  1. Разберите вакансию. Выпишите требуемые язык, фреймворки и тип задач. Отделите то, что уже умеете объяснять, от тем для повторения.
  2. Ответьте без подсказки. Например: что произойдёт при изменении ключа HashMap? Затем проверьте свой ответ по документации.
  3. Решите небольшую задачу. До кода уточните входные данные и ожидаемый результат, а после проверьте пустой ввод и другие границы.
  4. Объясните решение вслух. Назовите выбранную структуру данных, оценку сложности и то, что изменится при других требованиях.
  5. Разберите затруднения. Можно использовать собственные заметки или доступную сохранённую историю в Interview Ninja, чтобы вернуться к вопросам, на которых остановились. Подробнее о работе с историей рассказываем в материале об экспорте. Ответ ИИ стоит сверять с документацией.

А если хочется регулярно находить новые темы для подготовки и разборов IT-собеседований, загляните в Telegram-канал Interview Ninja.

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