Співбесіда 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 інтерв'ю на місяць