Собеседование Java: вопросы junior, middle и senior с ответами
На собеседовании Java проверяют не знание всего API, а понимание того, как язык и JVM работают внутри: как устроены equals/hashCode и HashMap, чем отличаются коллекции, что гарантирует модель памяти и как ведёт себя сборщик мусора. Для junior достаточно уверенного Java Core и коллекций, для middle добавляются многопоточность, Spring и работа с базой через JPA, для senior — JVM, производительность и архитектурные компромиссы. Готовиться лучше всего через вопросы «почему так устроено» и 3–5 типовых задач, которые вы можете решить вслух.
Ниже — как проходит интервью, какие темы и вопросы встречаются на каждом уровне, таблица «слабый ответ → сильный ответ», разбор задач с кодом и план подготовки. Общую структуру технических интервью описывает статья про вопросы на техническом собеседовании.
Как проходит собеседование на Java-разработчика
Типичный процесс состоит из нескольких этапов, состав зависит от компании:
- Скрининг с HR или рекрутером — опыт, ожидания, формат работы. Здесь пригодится умение рассказать о себе.
- Техническое интервью — 45–90 минут вопросов по теме: Java Core, коллекции, многопоточность, Spring, базы данных. Часто идёт по резюме: спрашивают про то, что вы сами написали в проектах.
- Live coding или задачи — решить небольшую задачу на коллекциях, строках или потоках, иногда в общем редакторе. Подход к такому формату разобран в статье про live coding.
- System design — для middle+ и senior: спроектировать сервис, обсудить хранение, очереди, масштабирование. См. system design на собеседовании.
- Финальный разговор с руководителем или командой — про процессы, ответственность, мотивацию.
Вопросы на собеседовании Java чаще всего растут из вашего резюме: указали Spring Boot — ждите вопросов про контейнер и автоконфигурацию, указали Kafka — про гарантии доставки. Не пишите в резюме то, о чём не готовы говорить 10 минут.
Что спрашивают по уровням
| Уровень | Темы | Что хотят увидеть |
|---|---|---|
| Junior | ООП, String и неизменяемость, коллекции, исключения, equals/hashCode, базовый SQL | Точные определения и умение написать простой код без подсказок |
| Middle | HashMap изнутри, многопоточность, Stream API, Spring (IoC, транзакции), JPA, индексы | Понимание причин и ограничений, а не только «как пользоваться» |
| Senior | JVM и GC, модель памяти, производительность, проектирование, отказоустойчивость | Решения с компромиссами и опыт разбора реальных проблем |
Это один путь роста, а не три разных экзамена: вопрос junior-уровня на senior-интервью задают с «а что будет, если…».
Вопросы на собеседовании Java junior
ООП и основы языка. Четыре принципа: инкапсуляция, наследование, полиморфизм, абстракция — но интервьюеру нужен пример, а не перечисление. Чем абстрактный класс отличается от интерфейса: у класса может быть состояние и конструктор, класс наследуется один, интерфейсов реализуют сколько угодно. Что такое перегрузка (выбор метода на этапе компиляции) и переопределение (выбор по реальному типу объекта во время выполнения).
String. Строки неизменяемы: поэтому они безопасны как ключи в HashMap, можно кэшировать hashCode и делиться между потоками. Конкатенация в цикле создаёт много временных объектов — используйте StringBuilder. Сравнивать строки по содержимому нужно через equals, а не ==.
equals и hashCode. Если два объекта равны по equals, их hashCode обязан совпадать; обратное неверно. Если переопределить только equals, объект «потеряется» в HashMap и HashSet.
Исключения. Checked (наследники Exception без RuntimeException) компилятор заставляет обрабатывать, unchecked (RuntimeException и Error) — нет. Блок finally выполняется почти всегда; для ресурсов лучше try-with-resources, который сам вызовет close().
Коллекции. ArrayList — массив с быстрым доступом по индексу, LinkedList — двусвязный список, на практике нужен редко. HashSet построен на HashMap, TreeMap держит ключи отсортированными, LinkedHashMap — в порядке вставки.
Вопросы для middle
HashMap изнутри
Это самый популярный вопрос на собеседовании Java. Структура: массив корзин, в каждой — связный список (или дерево) пар ключ-значение. Алгоритм put: вычислить hashCode ключа, преобразовать в индекс корзины, при коллизии пройти по цепочке и сравнить ключи через equals. Когда заполненность превышает load factor (по умолчанию 0,75), таблица увеличивается вдвое и записи перераспределяются. Если цепочка в корзине становится длинной, а таблица достаточно большая, Java 8+ превращает её в красно-чёрное дерево, чтобы в худшем случае поиск был за O(log n), а не O(n).
Следующие вопросы: что будет, если мутировать ключ после вставки (его не найдут, потому что изменится hash), почему HashMap небезопасен при записи из нескольких потоков, чем ConcurrentHashMap отличается от Collections.synchronizedMap (гранулярность блокировок и безопасные атомарные операции вроде computeIfAbsent).
Многопоточность
- Поток и процесс. Потоки внутри процесса делят память, поэтому нужна синхронизация.
synchronizedиvolatile.synchronizedдаёт взаимное исключение и видимость изменений;volatile— только видимость и запрет некоторых переупорядочиваний, но не атомарность составных операций.count++наvolatileне потокобезопасен.- Happens-before. Модель памяти Java описывает, когда запись одного потока гарантированно видна другому: освобождение монитора, запись
volatile,Thread.start()иjoin(). - Пулы потоков.
ExecutorService, размер пула и очередь задач; почему не стоит создаватьnew Threadна каждый запрос. - Deadlock. Возникает, когда потоки захватывают блокировки в разном порядке. Лечится единым порядком захвата или
tryLockс таймаутом. CompletableFuture. Композиция асинхронных задач; важно уметь сказать, в каком пуле выполняются шаги.
Spring и работа с базой
- IoC и DI. Контейнер создаёт объекты и внедряет зависимости; предпочтительно внедрение через конструктор — зависимости явные и поля можно сделать
final. - Область видимости бинов. По умолчанию singleton; singleton с изменяемым состоянием — частый источник багов в многопоточной среде.
@Transactional. Работает через прокси, поэтому вызов транзакционного метода из того же класса (self-invocation) транзакцию не откроет. Откат по умолчанию происходит на unchecked-исключениях.- JPA и Hibernate. Проблема N+1: загрузка списка сущностей и затем отдельного запроса на каждую связь. Лечится
JOIN FETCH, entity graph или пакетной загрузкой.LAZYпротивEAGER, жизненный цикл сущности. - SQL. Типы JOIN, индексы, уровни изоляции — подробно в статье про вопросы по SQL.
Вопросы для senior
JVM и сборка мусора
- Области памяти. Куча (объекты), стеки потоков (кадры методов, локальные переменные), Metaspace (метаданные классов). Объекты чаще всего умирают молодыми, поэтому куча делится на молодое и старое поколение.
- Как работает GC. Достижимость от GC roots: локальные переменные в стеках, статические поля. Недостижимые объекты подлежат сборке. Современные сборщики (G1, ZGC и другие) различаются компромиссом между паузами и пропускной способностью — конкретный выбор зависит от версии JVM и нагрузки.
- Утечки памяти в Java. Это не потерянные указатели, а объекты, которые продолжают быть достижимыми: статические коллекции-кэши без ограничения, незакрытые ресурсы, слушатели,
ThreadLocalв пуле потоков. - Разбор проблем. Как диагностировать: дамп кучи, логи GC, профилировщик, thread dump при зависании. Важно описать порядок действий, а не перечислить инструменты.
Проектирование и надёжность
Senior-вопросы чаще открытые: как обеспечить идемпотентность обработки сообщения, как сделать ретраи безопасными, где в системе нужны транзакции, а где допустима согласованность в конечном счёте. Хорошо подготовиться к обсуждению микросервисов и монолита и общим подходам из system design.
Таблица: вопрос, слабый ответ, сильный ответ
| Вопрос | Слабый ответ | Сильный ответ |
|---|---|---|
| Зачем переопределять hashCode вместе с equals? | «Так требует IDE» | Равные по equals объекты должны давать один hashCode, иначе HashMap/HashSet не найдут ключ: поиск идёт по корзине, вычисленной из hash |
| Почему String неизменяема? | «Так сделали разработчики» | Безопасность как ключа и параметра, кэширование hashCode, потокобезопасность, возможность пула строк |
| Чем HashMap отличается от ConcurrentHashMap? | «Второй потокобезопасный» | HashMap небезопасен при конкурентной записи; ConcurrentHashMap допускает параллельные операции без общей блокировки и даёт атомарные merge, computeIfAbsent |
| Что делает volatile? | «Синхронизирует переменную» | Гарантирует видимость записи другим потокам и порядок, но не атомарность: count++ остаётся гонкой, нужен AtomicInteger или synchronized |
Почему @Transactional не сработал? | «Баг Spring» | Вызов внутри того же класса идёт мимо прокси; метод должен быть вызван через бин, а исключение — unchecked либо с настроенным rollbackFor |
| Что такое проблема N+1? | «Медленные запросы» | На список из N сущностей выполняется 1 запрос плюс N запросов на ленивую связь; лечится JOIN FETCH, entity graph, пакетной загрузкой |
| Бывают ли утечки памяти в Java при наличии GC? | «Нет, есть сборщик» | Бывают: объекты остаются достижимыми через статические кэши, слушатели, ThreadLocal; поиск — через дамп кучи и анализ путей к GC roots |
| Как избежать deadlock? | «Не использовать потоки» | Единый порядок захвата блокировок, tryLock с таймаутом, минимальная область блокировки, предпочтение неизменяемым данным и высокоуровневым утилитам |
| Abstract class или interface? | «Это почти одно и то же» | Класс хранит состояние и общий код, наследуется один; интерфейс описывает контракт, реализуется многими; выбор — от того, есть ли общее состояние |
Если на собеседовании вы отвечаете как в левом столбце, вопрос быстро превращается в допрос. Сильный ответ не обязан быть длинным: определение, причина, ограничение и короткий пример.
Задачи с решениями
Задача 1. Ключ, потерянный в HashSet
Почему contains вернёт false, если в классе нет equals и hashCode, и как это исправить?
import java.util.*;
final class Point {
private final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() { return Objects.hash(x, y); }
}
Без этих методов используются идентичность объекта (==) и системный hash, поэтому новый Point(1, 2) не равен добавленному раньше. С ними set.contains(new Point(1, 2)) вернёт true. Поля, участвующие в hashCode, должны быть неизменяемыми.
Задача 2. Счётчик из нескольких потоков
Четыре потока по 100 000 раз вызывают count++. Итог почти всегда меньше 400 000. Почему и как исправить?
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger value = new AtomicInteger();
void inc() { value.incrementAndGet(); }
int get() { return value.get(); }
}
count++ — три шага (прочитать, увеличить, записать), и потоки перезаписывают результаты друг друга. AtomicInteger делает операцию атомарной без блокировки; альтернатива — метод с synchronized. Хороший ответ добавляет: при очень высокой конкуренции за один счётчик можно использовать LongAdder.
Задача 3. Первый неповторяющийся символ
Найти первый символ строки, который встречается ровно один раз.
import java.util.*;
static Character firstUnique(String s) {
Map<Character, Integer> counts = new LinkedHashMap<>();
for (char c : s.toCharArray()) {
counts.merge(c, 1, Integer::sum);
}
for (Map.Entry<Character, Integer> e : counts.entrySet()) {
if (e.getValue() == 1) return e.getKey();
}
return null;
}
LinkedHashMap сохраняет порядок вставки, поэтому первая запись со счётчиком 1 — искомая. Сложность O(n) по времени и памяти. Для "swiss" результат — 'w', для "aabb" — null. Такие задачи — из того же ряда, что и алгоритмы и структуры данных на собеседовании.
Задача 4. Сотрудник с максимальной зарплатой в каждом отделе
Дан список записей Employee(name, dept, salary). Нужно получить для каждого отдела сотрудника с максимальной зарплатой.
import java.util.*;
import java.util.stream.*;
record Employee(String name, String dept, int salary) {}
static Map<String, Optional<Employee>> topByDept(List<Employee> emps) {
return emps.stream().collect(
Collectors.groupingBy(
Employee::dept,
Collectors.maxBy(Comparator.comparingInt(Employee::salary))
)
);
}
groupingBy строит карту «отдел → список», а нижестоящий коллектор maxBy вместо списка оставляет одного. Результат обёрнут в Optional, потому что группа теоретически может быть пустой — об этом полезно сказать вслух.
Задача 5. LRU-кэш
Реализовать кэш на N элементов, вытесняющий давно не использованные.
import java.util.*;
class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
LruCache(int capacity) {
super(16, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
Третий параметр конструктора true включает порядок по обращениям: get переносит запись в конец. После каждой вставки вызывается removeEldestEntry, и самая старая запись удаляется. Класс не потокобезопасен — если спросят, ответ: обернуть в Collections.synchronizedMap или взять готовую библиотеку кэширования.
Типичные ошибки на собеседовании
- Заученные определения без причин. «Интерфейс — это контракт» без примера выглядит как зубрёжка. Добавляйте «потому что» и «в моём проекте».
- Путаница с гарантиями. Утверждать, что
volatileделает операцию атомарной, или чтоHashMapупорядочен. - Молчание при задаче. Интервьюер оценивает рассуждение. Проговаривайте план, граничные случаи и сложность.
- Игнорирование особых случаев: пустая строка,
null, дубликаты, большие входные данные. - Выдуманный опыт. Если вы не работали с Kafka, скажите, что читали о ней, и объясните общее устройство. Честное «не знаю, но рассуждал бы так» лучше уверенной ошибки.
- Нет вопросов в конце. Подготовьте несколько — подсказки в статье про вопросы работодателю.
Чек-лист подготовки к собеседованию Java
- Составьте список из 30–40 вопросов по темам выше и ответьте на каждый вслух за минуту-две.
- Напишите руками без IDE:
equals/hashCode, потокобезопасный счётчик, LRU-кэш, пару стримов. - Нарисуйте схему HashMap и путь запроса через Spring: контроллер, сервис, транзакция, репозиторий, база.
- Перечитайте свои проекты: для каждого сформулируйте задачу, решение, сложность и ошибку, которую исправили.
- Повторите SQL: JOIN, индексы, транзакции, план запроса.
- Для middle+ подготовьте один рассказ про инцидент или оптимизацию: симптом, диагностика, причина, результат.
- Сделайте пробное собеседование с другом или запишите себя на видео, чтобы поймать паразитные слова и паузы.
- Если остался один день, используйте план из статьи как подготовиться к собеседованию за день.
Где здесь помогает Interview Assistant
Interview Assistant — приложение для macOS и Windows, которое транскрибирует речь интервьюера в реальном времени и отвечает на вопрос сначала краткими тезисами, а затем развёрнутым разбором. Для подготовки к интервью по Java это удобный тренажёр: вы прогоняете вопросы из списка выше, сравниваете свой ответ с тезисами и находите темы, где проваливаетесь, например, модель памяти или транзакции в Spring. Первые 20 минут бесплатны, этого хватает, чтобы оценить формат.
Ассистент не заменяет знания: он помогает структурировать ответ и вспомнить деталь, но объяснять свой опыт и решать задачу должны вы. Лучший результат — когда вы подготовились заранее, а подсказка нужна только как страховка от волнения.
Частые вопросы
Сколько вопросов задают на собеседовании Java?
Обычно техническое интервью длится 45–90 минут, за это время обсуждают 10–20 тем, часть из них — по вашему резюме. Один вопрос часто разворачивается в цепочку уточнений, поэтому глубина важнее количества.
Чем отличается собеседование Java junior и middle?
У junior проверяют основы: ООП, коллекции, исключения, строки, простой SQL, и умение написать небольшой код. Middle обязан объяснять внутреннее устройство (HashMap, транзакции в Spring, пулы потоков) и находить причины проблем, а не только использовать готовые инструменты.
Нужно ли знать Spring для собеседования на Java разработчика?
Для большинства коммерческих позиций да: Spring Boot — де-факто стандарт, и вопросы про внедрение зависимостей, транзакции и работу с базой встречаются на уровне middle почти всегда. Для позиций по чистому Java Core или алгоритмам акцент может быть иным — это видно по описанию вакансии.
Что учить в первую очередь, если времени мало?
Начните с equals/hashCode и HashMap, коллекций, многопоточных основ (synchronized, volatile, пулы потоков) и транзакций в Spring. Эти темы встречаются чаще всего и дают самый большой эффект на коротком отрезке.
Как отвечать, если не знаешь ответ?
Скажите прямо, что не работали с этим, и покажите, как бы вы рассуждали: назовите смежные знания, предположите поведение и объясните, как бы проверили. Интервьюеры ценят честность и способность разбираться, а не уверенную неправду.
Как готовиться к задачам на live coding на Java?
Практикуйтесь решать небольшие задачи на коллекциях, строках и потоках в простом редакторе, без автодополнения, и проговаривайте решение вслух. Тренируйте типовой набор: подсчёт частот, группировка, LRU-кэш, потокобезопасный счётчик, обход списков и деревьев. Похожий набор для другого языка — в статье про собеседование Python.