Перейти к содержимому

Собеседование Java: вопросы junior, middle и senior с ответами

Команда Interview Assistantтехнические вопросыjava

На собеседовании Java проверяют не знание всего API, а понимание того, как язык и JVM работают внутри: как устроены equals/hashCode и HashMap, чем отличаются коллекции, что гарантирует модель памяти и как ведёт себя сборщик мусора. Для junior достаточно уверенного Java Core и коллекций, для middle добавляются многопоточность, Spring и работа с базой через JPA, для senior — JVM, производительность и архитектурные компромиссы. Готовиться лучше всего через вопросы «почему так устроено» и 3–5 типовых задач, которые вы можете решить вслух.

Ниже — как проходит интервью, какие темы и вопросы встречаются на каждом уровне, таблица «слабый ответ → сильный ответ», разбор задач с кодом и план подготовки. Общую структуру технических интервью описывает статья про вопросы на техническом собеседовании.

Как проходит собеседование на Java-разработчика

Типичный процесс состоит из нескольких этапов, состав зависит от компании:

  1. Скрининг с HR или рекрутером — опыт, ожидания, формат работы. Здесь пригодится умение рассказать о себе.
  2. Техническое интервью — 45–90 минут вопросов по теме: Java Core, коллекции, многопоточность, Spring, базы данных. Часто идёт по резюме: спрашивают про то, что вы сами написали в проектах.
  3. Live coding или задачи — решить небольшую задачу на коллекциях, строках или потоках, иногда в общем редакторе. Подход к такому формату разобран в статье про live coding.
  4. System design — для middle+ и senior: спроектировать сервис, обсудить хранение, очереди, масштабирование. См. system design на собеседовании.
  5. Финальный разговор с руководителем или командой — про процессы, ответственность, мотивацию.

Вопросы на собеседовании Java чаще всего растут из вашего резюме: указали Spring Boot — ждите вопросов про контейнер и автоконфигурацию, указали Kafka — про гарантии доставки. Не пишите в резюме то, о чём не готовы говорить 10 минут.

Что спрашивают по уровням

УровеньТемыЧто хотят увидеть
JuniorООП, String и неизменяемость, коллекции, исключения, equals/hashCode, базовый SQLТочные определения и умение написать простой код без подсказок
MiddleHashMap изнутри, многопоточность, Stream API, Spring (IoC, транзакции), JPA, индексыПонимание причин и ограничений, а не только «как пользоваться»
SeniorJVM и 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

  1. Составьте список из 30–40 вопросов по темам выше и ответьте на каждый вслух за минуту-две.
  2. Напишите руками без IDE: equals/hashCode, потокобезопасный счётчик, LRU-кэш, пару стримов.
  3. Нарисуйте схему HashMap и путь запроса через Spring: контроллер, сервис, транзакция, репозиторий, база.
  4. Перечитайте свои проекты: для каждого сформулируйте задачу, решение, сложность и ошибку, которую исправили.
  5. Повторите SQL: JOIN, индексы, транзакции, план запроса.
  6. Для middle+ подготовьте один рассказ про инцидент или оптимизацию: симптом, диагностика, причина, результат.
  7. Сделайте пробное собеседование с другом или запишите себя на видео, чтобы поймать паразитные слова и паузы.
  8. Если остался один день, используйте план из статьи как подготовиться к собеседованию за день.

Где здесь помогает 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.