Собеседование Java-разработчика охватывает семантику языка, коллекции, многопоточность, Spring и умение рассуждать о JVM. Ниже — вопросы, которые задают чаще всего, каждый с эталонным ответом. Middle: более глубокое понимание, оптимизацию и реальные рабочие ситуации.
1
Как устроен HashMap внутри и что изменилось в Java 8?
Ответ
Записи лежат в массиве бакетов, индекс берётся из функции хеша ключа; коллизии складываются в связный список внутри бакета. В Java 8 появилась treeification: как только в бакете больше восьми записей, он превращается в красно-чёрное дерево, и худший случай поиска стал O(log n) вместо O(n). Плюс хеш размешивается через h ^ (h >>> 16), чтобы старшие биты влияли на младший индекс.
2
Что гарантирует volatile, а что нет?
Ответ
Гарантирует видимость и упорядочивание: запись сразу публикуется другим потокам, а чтения не кешируются в регистре. Не гарантирует атомарность, поэтому volatile-счётчик всё равно теряет инкременты — ++ это чтение-изменение-запись, и два потока могут прочитать одно значение. volatile берут для флага, который читают многие, а AtomicInteger или synchronized — когда операция объединяет чтение и запись.
3
Объясните propagation у @Transactional. Когда нужен REQUIRES_NEW?
Ответ
Propagation решает, что происходит при вызове транзакционного метода изнутри другой транзакции. REQUIRED по умолчанию присоединяется к существующей, поэтому откат в любом месте откатывает всё. REQUIRES_NEW приостанавливает внешнюю и открывает свою — это нужно для аудит-лога или уведомления, которое должно пережить падение вызывающего кода. Классический баг — ожидать от REQUIRED изоляции сбоя, которой у него нет.
4
В эндпоинте на Hibernate видна проблема N+1. Как найти и починить?
Ответ
Находится включением логирования SQL и подсчётом запросов на один вызов — признак один select и следом сотни почти одинаковых. Возникает, когда загрузили родителей и обращаетесь к ленивой коллекции у каждого. Лечится JOIN FETCH в JPQL, entity graph, @BatchSize для загрузки пачками или проекцией, которая выбирает только нужные ответу поля. Перевод в EAGER это не лечение, а перенос проблемы.
5
Чем Stream отличается от цикла for и когда цикл лучше?
Ответ
Stream описывает конвейер ленивых операций, запускаемый терминальной, и держит фильтрацию с преобразованием читаемыми. Цикл лучше в горячем участке, где абстракция стоит измеримого времени, в логике с побочными эффектами и там, где break посреди выразительнее, чем takeWhile. Настоящая ловушка — параллельные стримы: они помогают только на большом объёме вычислительной работы и обычно вредят в остальных случаях.
6
Сервис тормозит, а CPU низкий. Куда смотреть?
Ответ
Низкий CPU при плохой пропускной способности означает ожидание, а не вычисление: блокирующий ввод-вывод, исчерпанный пул соединений, конкуренция за блокировки или внешний вызов без таймаута. Снимите thread dump и посмотрите, на чём реально стоят потоки. Чаще всего виноват исчерпанный пул соединений, и обычно это следствие транзакции, открытой на время сетевого вызова.
7
Чем автоконфигурация Spring Boot отличается от обычного @Configuration?
Ответ
Автоконфигурация — это условная конфигурация, поставляемая стартером: классы с @ConditionalOnClass или @ConditionalOnMissingBean, которые применяются, только если зависимость есть в classpath и вы сами бин не объявили. Последнее и делает её одновременно волшебной и предсказуемой: ваш бин всегда побеждает, и именно так переопределяется любой дефолт.
8
Чем различаются `wait()`, `sleep()` и `join()`?
Ответ
sleep приостанавливает текущий поток и удерживает захваченную блокировку — так и получают дедлок с невинным на вид кодом. wait освобождает монитор и паркует поток до notify, и именно поэтому вызывать его можно только внутри synchronized. join ждёт завершения другого потока. wait и notify принадлежат Object, а не Thread, потому что блокировка принадлежит объекту.
9
Что делает `transient` и где кусается сериализация?
Ответ
transient исключает поле из сериализации, поэтому при десериализации оно возвращается значением по умолчанию. Помечают им всё вычисляемое, всё крупное и всё секретное: паролю или живому соединению в потоке байтов делать нечего. Более широкая ловушка в том, что сериализация замораживает форму класса в формат: измените поля — и старые данные не прочитаются, если не продумали serialVersionUID и совместимость, поэтому большинство команд берут JSON или protobuf.
🦎
Прочитать ответы недостаточно
На собеседовании ты говоришь вслух под давлением. Кем задаст эти же вопросы, оценит каждый ответ и покажет, что именно подтянуть.
Пройти интервью Middle Java Backend →Бесплатно · 3 интервью в месяц