Всем привет!
Сейчас я работаю Senior Java Developer в банке, и за последние годы мне довелось пройти немало собеседований - разных по уровню, стилю и степени жесткости. Сегодня я хочу рассказать об одном из них и поделиться опытом, который может быть полезен тем, кто тоже готовится к новым вызовам.
В моем профиле есть шпаргалки для подготовки к собесам:
1. Задача на понимание работы наследования
Есть следующий код:
public class First {
protected int count;
public First() {
System.out.println("First");
calculate();
}
public void calculate() {
System.out.println(count);
}
@Override
public int hashCode() {
return 0;
}
}
class Second extends First {
public Second() {
this.count = 5;
System.out.println("Second");
calculate();
}
public void calculate() {
this.count++;
System.out.println(count);
}
@Override
public int hashCode() {
return 0;
}
}
class Main {
public static void main(String[] args) {
Second s = new Second();
}
}
Что выведет код?
Ответ и пояснение
First
1
Second
6
Пояснение
При создании объекта
Second s = new Second();сначала вызывается конструктор родительского классаFirst-
В конструкторе
First()System.out.println("First")- выводит "First"calculate()→ вызывает переопределенный метод из классаSecond
-
Затем выполняется конструктор
Second()this.speed = 5→ speed становится 5System.out.println("Second")→ выводитSecondcalculate()→ снова вызываетсяSecond.calculate()где идетcount++
2. Знание контракта между Equals и HashCode
Есть код(классы такие же как и в задаче выше), что он выведет:
public static void main(String[] args) {
HashSet<Object> set = new HashSet<>();
set.add(new First());
set.add(new Second());
set.add(new Second());
System.out.println("Размер:" + set.size());
}
Ответ
Размер:3
Пояснение
new First() - добавляется (хэшкод = 0)
-
new Second() - проверяется:
Хэшкод = 0 (совпадает)
equals()по умолчанию сравнивает ссылки → разные объекты → добавляется
-
new Second() - еще один новый объект:
Хэшкод = 0 (совпадает)
equals()сравнивает ссылки → это третий уникальный объект → добавляется
Метод equals() по умолчанию (из класса Object) сравнивает ссылки на объекты, а не их содержимое. Поэтому каждый new Second() создает новый объект с новой ссылкой, и все они считаются разными.
Тут важно отменить, что даже в случае, если hashCode не будет совпадать, то все равно получим Размер:3
Но если мы переопределим equals и hashCode:
@EqualsAndHashCode // в качестве примера взял аннотацию из lombok
public class First {
}
@EqualsAndHashCode
class Second extends First {
}
То результат уже будет Size:2
3. Устройство HashMap
Я понимаю, что уже почти в каждом углу говорилось про HashMap. Я расскажу очень коротко(если хотите почитать подробнее и углубиться, то можете посмотреть тут)
Ответ(короткий)
HashMap — это массив корзин. Индекс выбираем по hashCode, а столкновения (коллизии) решаем сравнениями через equals.
Алгоритм вставки:
Считаем
hashCode()и определяем корзину.Если корзина пуста - вставляем.
-
Если в корзине есть элементы:
ищем тот же ключ через
equalsесли найден → заменяем значение
если нет → добавляем новый элемент (список → дерево при >8 элементов)
При заполненности > loadFactor происходит resize.
4. Зачем нужны бинарные деревья и их сложность
Про бинарные деревья тоже говорили уже везде, подробная инфа тут
Ответ(короткий)
Зачем нужны бинарные деревья:
Чтобы хранить данные в отсортированном виде и быстро выполнять поиск, вставку и удаление.
Сложность поиска в BST:
лучший случай (сбалансировано): O(log n)
худший случай (вырождено в список): O(n)
Сложность вставки:
лучший случай: O(log n)
худший случай: O(n)
5. Какие есть виды GC и в чем их отличие
Об этом я рассказывал тут
Ответ
Serial GC
Использует один поток для всех фаз GC.
Подходит для однопоточных приложений и небольших heap.
Алгоритм: "copying" (в Young Gen) и "mark-sweep-compact" (в Old Gen).
Параметр:
-XX:+UseSerialGC
Parallel GC (Throughput Collector)
Использует несколько потоков для работы в Young и Old Gen.
Цель — максимальная пропускная способность, а не минимизация пауз.
Подходит для серверных приложений без строгих требований к задержкам.
Параметр:
-XX:+UseParallelGC
CMS (Concurrent Mark Sweep) [устарел]
Работает параллельно с приложением (concurrent), уменьшая stop-the-world паузы.
Этапы: initial mark, concurrent mark, remark, sweep.
Не компактизирует память (может привести к фрагментации).
Устарел начиная с Java 9 и удалён в Java 14
Параметр:
-XX:+UseConcMarkSweepGC
G1 GC (Garbage First)
Делит heap на множество регионов.
Каждый регион может быть частью Young или Old Generation.
Этапы GC включают: Initial Mark, Concurrent Mark, Remark, Cleanup, Copy.
Работает по принципу "сборка сначала самых мусорных регионов" (Garbage First).
Использует предсказуемые паузы и старается не превышать
MaxGCPauseMillisПоддерживает инкрементальную, concurrent и компактизирующую сборку Old Gen.
G1 ведёт статистику "полезности" регионов и выбирает наиболее эффективные для сборки.
Параметр:
-XX:+UseG1GCПо умолчанию используется с Java 9+
ZGC (Z Garbage Collector)
Поддерживает heap до терабайт.
Работает с паузами менее 10 мс, независимо от размера heap.
Полностью concurrent (почти все фазы выполняются параллельно с приложением).
Подходит для latency-чувствительных систем.
Параметр:
-XX:+UseZGC
Shenandoah
Похож на ZGC, с акцентом на короткие паузы.
Использует concurrent compacting.
Поддерживается OpenJDK.
Параметр:
-XX:+UseShenandoahGC
6. Типы ссылок в java
Ответ
Strong Reference (сильная ссылка)
Это обычные ссылки, которые мы используем каждый день.
Пока на объект существует хотя бы одна сильная ссылка - он не подлежит сборке мусора.
Чтобы объект мог быть собран, все сильные ссылки на него должны быть обнулены.
Object obj = new Object();
Soft Reference (мягкая ссылка)
Объект удаляется только при нехватке памяти.
Используется в кешах, чтобы не загружать память, но сохранить объект, если он ещё полезен.
Можно получить объект через
ref.get(), но если GC уже удалил его - вернётсяnull.
SoftReference<Object> ref = new SoftReference<>(new Object());
Weak Reference (слабая ссылка)
Объект может быть собран немедленно, даже если только слабые ссылки на него остались.
Используется для реализации структур с автоудалением (например,
WeakHashMap).Часто применяется, когда объект должен быть доступен «до тех пор, пока он кому-то нужен».
WeakReference<Object> ref = new WeakReference<>(new Object());
Phantom Reference (фантомная ссылка)
Объект уже помечен как удаляемый, но ещё не собран GC.
Метод
get()всегда возвращаетnull.Используется для контроля финализации и освобождения ресурсов вне heap (например, off-heap, native).
Требует
ReferenceQueue, через которую можно узнать, что объект вот-вот будет удалён.
PhantomReference<Object> ref = new PhantomReference<>(new Object(), referenceQueue);
7. Назови 5 классов из пакеты concurrent и зачем они нужны
Ответ
1) CompletableFuture - позволяет запускать асинхронные задачи, комбинировать их, цеплять колбэки и работать без блокировок. Упрощает параллелизм.
2) ConcurrentHashMap - потокобезопасный HashMap. Позволяет многим потокам одновременно читать и обновлять данные без общего большого локa.
3) Phaser - продвинутый синхронизатор, позволяет синхронизировать потоки по фазам (этапам). Гибче, чем CyclicBarrier/CountDownLatch.
4) AtomicInteger - примитив для атомарных операций над int без использования локов (CAS). Нужен для счетчиков, флагов и инкрементов между потоками.
5) ReentrantLock - явная блокировка с расширенными возможностями: tryLock, fairness, condition-переменные. Более гибкая альтернатива synchronized.
8. Расскажи про DeadLock и LiveLock
Ответ
Deadlock (взаимная блокировка)
Потоки навсегда блокируют друг друга, каждый ждёт ресурс, удерживаемый другим.
Итог: система стоит, прогресса нет.
Пример:
Поток A держит ресурс 1 и ждёт ресурс 2.
Поток B держит ресурс 2 и ждёт ресурс 1.
Как избегать:
Всегда блокировать ресурсы в одном порядке.
Использовать таймауты при захвате блокировок (
tryLock(timeout)).Минимизировать количество одновременно захватываемых блокировок.
Livelock (ожившая блокировка)
Потоки не заблокированы, но бесполезно двигаются, постоянно пытаясь избежать конфликта и мешая друг другу.
Итог: система работает, но прогресса тоже нет.
Пример:
Два потока уступают друг другу ресурс, отказываются и пытаются снова, но синхронно, и бесконечно.
Как избегать:
Добавлять рандомные задержки или экспоненциальный бэкофф при повторных попытках.
Использовать явные таймауты и прекращать попытки через определённое время.
Пересматривать алгоритм кооперации, чтобы не блокировать друг друга непрерывно.
9. SQL задачка
Есть такая структура бд:

Нужно написать запрос, который вернет имя и общую сумму заказов клиента. Клиент при этом должен быть активным, а сумма заказов больше 0
Ответ
Мой запрос выглядит так:
SELECT
c.name AS company_name,
SUM(p.price) AS product_sum
FROM client c
JOIN product p ON c.id = p.client_id
WHERE c.is_active
GROUP BY c.id, c.name
HAVING SUM(p.price) > 0
ORDER BY product_sum DESC;
10. В чем разница между having и where
Ответ
Время применения
WHERE- фильтрация до группировки (на уровне отдельных строк)HAVING- фильтрация после группировки (на уровне групп)
С чем работают
WHERE- работает с отдельными записями и обычными полямиHAVING- работает с результатами агрегатных функций (SUM,COUNT,AVGи т.д.)
Использование с GROUP BY
WHERE- может использоваться безGROUP BYHAVING- используется вместе сGROUP BY
11. Что такое explain plan и для чего он нужен
Ответ
EXPLAIN PLAN — это план выполнения SQL-запроса, показывающий, какие операции СУБД будет выполнять (сканирование таблицы, использование индекса, типы join’ов и т.д.). Он нужен для понимания того, где находятся узкие места и что можно оптимизировать — например, добавить индекс, поменять тип соединения или переписать запрос.
12. Какие есть уровни изоляции транзакции и какие проблемы в них присутствуют?
Ответ
Есть прекрасная табличка из официальной:

13. Понимание proxy в spring
Есть базовые задачки с транзакциями, но на этом собесе мне задавали вопросы про @Cacheable. Есть примера кода, в котором кэш не работает, как сделать так, чтобы он заработал:
@Service
@EnableCaching
public class CacheClass {
@SneakyThrows
@PostConstruct
public void init() {
System.out.println(test(1));
Thread.sleep(1000);
System.out.println(test(2));
Thread.sleep(1000);
System.out.println(test(1));
}
@Cacheable(cacheNames = "test", key = "#integer")
public String test(int integer) {
return LocalDateTime.now().toString();
}
}
Ответ
Здесь можно сразу предложить несколько вариантов по аналогии с транзакциями
Сделать self inject и вызвать метод через него
Использовать
applicationContext.getBean(CacheClass.class);
Для @Transaction это было 100% сработало бы, но в Cacheable есть подводный камень.
Ни один из верхних вариантов не сработает. Подумай еще, как можно это решить?
Ответ для Cacheable
Для @Cacheable ситуация ещё хуже: кеш-аспект инициализируется позднее, поэтому вызов @Cacheable из @PostConstruct обычно не срабатывает — об этом есть явное issue в Spring
Тут вы можете применить ApplicationRunner или CommandLineRunner, которые смогу помочь, просто заимплементить его:
@Service
@EnableCaching
public class CacheClass implements ApplicationRunner {
@Autowired
@Lazy
private CacheClass self;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(self.test(1));
Thread.sleep(1000);
System.out.println(self.test(2));
Thread.sleep(1000);
System.out.println(self.test(1));
}
@Cacheable(cacheNames = "test", key = "#integer")
public String test(int integer) {
return LocalDateTime.now().toString();
}
}
14. Приведите пример каждого из типа паттернов(поведенческий, порождающий, структурный) и назовите ваш самый любимый паттерн и как вы его применяли.
Ответ
Паттернов очень много, поэтому я приведу вам эту картинку:

Мой же самый любимый паттерн это шаблонный метод.
Шаблонный метод используют, когда есть общий алгоритм, который повторяется у разных обработчиков, но некоторые шаги в нём должны отличаться. Общая логика фиксируется в базовом классе, а изменяющиеся части выносятся в абстрактные методы и уже конкретные реализации определяют, что происходит на этих этапах. Это позволяет избежать дублирования кода и чётко разделить общие и различающиеся шаги алгоритма.
Этот паттерн выручал меня не один раз)
15. Когда использовать реляционные базы, а когда нет. В чем разница между SQL и NoSQL базой
Ответ
SQL бд:
Сложные запросы и JOIN — когда нужны агрегации и связи между таблицами
Транзакции ACID — банковские операции, финансовые системы
Структурированные данные — четкая схема, предсказуемая структура
Целостность данных — внешние ключи, constraints
Отчетность и аналитика — сложные SQL-запросы
Подходят для: банковские системы (транзакции), медицинские записи (целостность данных), интернет-магазины (заказы, inventory)
NoSQL бд:
Большие объемы данных — Big Data, логгирование
Горизонтальное масштабирование — распределенные системы
Гибкая схема — часто меняющаяся структура данных
Высокая производительность записи — IoT, сенсоры, clickstream
Неструктурированные данные — JSON, документы, графы
Подходят для: проекты, где работают с документами, где нужно обрабатывать огромные объемы данных(например я знаю, что в VK Video используют cassandra в качестве БД), структура данных часто меняется
16. Задачка на system design
Мы работаем в страховой компании и предоставляем клиентам услуги по оформлению полисов. Когда приходит запрос от клиента, нам нужно обратиться к сторонней SOAP-системе через HTTP, чтобы получить необходимые данные для расчёта. По требованиям мы должны дать клиенту ответ в течение двух минут. Если за это время расчёт не завершён — мы обязаны вернуть ошибку.
Проблема в том, что эта внешняя SOAP-система в среднем отвечает около 40 секунд, но работает нестабильно: иногда очень медленно, иногда вообще не отвечает. При этом ожидаемая нагрузка — до 15 000 запросов в секунду, то есть система должна выдерживать высокий поток обращений и не зависеть от её нестабильности.
Скажу сразу, в этой задаче нет четкого одного ответа. Я же расскажу свой ответ:
Ответ
Я пойду по порядку:
Я бы сделал gate-way шлюз для авторизации и аутентификации пользователя(OAuth, Keycloak)
Асинхронная модель(архитектура событий) - SOAP-система нестабильная, выдает ответ +-40 сек, SLA ≤ 2 минут - асинхронный подход максимально оправдан. Клиенту не нужно ждать - даёте taskId и он проверяет статус
-
Микросервисная архитектура, я выделил несколько сервисов:
Main-service - только бизнес-логика: создание задач, хранение статуса, оркестрация.
Adapter-service - работа с внешней системой, ретраи, circuit breaker.
Timeout-service - только таймеры и SLA. (Я бы использовал delay queue для отслеживания таймера)
Gate-way service - авторизация, аутентификация, переадресация пользователей
В Adapter-service нужны ретраи, circuit breaker, fallback
Main-service обрабатывает всю логику, также он прослушивает ответ от adapter-service и timeout-service + main хранит информацию о сообщениях(outbox таблица)
Timeout-service можно реализовать с помощью delay queue для отслеживания таймера
-
Мы будем использовать kafka + outbox + de-dup таблица, чтобы гарантировать доставку и обработку 1 раз.
Outbox гарантирует доставку события.
Идентификаторы сообщений — защита от дублей в потребителе.
Kafka — выдержит твой TPS с огромным запасом.
Для отслеживания состояний можно использовать distributed tracing
Архивный топик (fan-out topic) - мы отбрасываем сообщения в отдельный топик, который на данный момент никто не слушает, чтобы была возможность к нему подключиться и прослушать все сообщения, даже, если kafka уже удалила их из другого топика.(Это можно сделать, если в будущем видится добавление сервисов обработки и есть на это доп ресурсы)
Если в дальнейшем видится рост клиентов, можно использовать Kafka Streams для работы с большими данными
Минусы такого подхода:
сложнее логирование
сложнее трассировка
нагрузка на инфраструктуру
сложнее тестирование
Плюсы такого подхода:
масштабируемость
гибкость / расширяемость
отказоустойчивость
высокая пропускная способность
Примерная схема взаимодействия сервисов:

Итог
Сегодня мы прошли через пример полного собеседования на позицию Senior Java Developer. Конечно, это не универсальный сценарий - у каждого интервью свои нюансы. Но это реальный опыт, который был у меня, и я постарался показать, какие вопросы и ситуации могут встретиться, а на что стоит обратить особое внимание. Надеюсь, это поможет вам подготовиться и подойти к собесу более уверенно.
Всем спасибо за внимание, удачных собесов и хорошего дня!)
Комментарии (18)

VGoudkov
22.11.2025 13:13Спасибо за материал, кратко и со вкусом!
На мой взгляд в шпаргалках (и видимо вопросах на собесах) не хватает чего-то наподобие "Как вы будете разбираться с приложением, которое упало по OOM", "Как вы будете выяснять, почему приложение иногда перестаёт отвечать в рамках SLA (а потом опять начинает, само да...).
Я про то, что JMC, VisualVM, Profiler в IDEA и это вот наше всё :)

KIL2
22.11.2025 13:13HAVING- работает с результатами агрегатных функций (SUM,COUNT,AVGи т.д.)
Это утверждение не корректно. Оператор
SELECT, содержащий функцииSUM,COUNT,AVGи т.д., отрабатывает ПОСЛЕ оператораHAVING, т.е.HAVINGникак не может работать с РЕЗУЛЬТАТАМИ агрегатных функций. Как раз, наоборот, агрегатные функции работают с результатами работы оператораHAVING.
VGoudkov
22.11.2025 13:13HAWING - это WHERE для результатов агрегатов. Т.е. отобрать всех клиентов, которые делали больше, чем три покупки за последнюю неделю.

KIL2
22.11.2025 13:13HAVING - это аналог WHERE именно для результата группировки (GROUP BY), а не агрегатов. Да, в HAVING возможно использование результатов агрегатов, но не только их. Но в HAVING могут использоваться не только результаты агрегатов, но и значения столбцов, использованных в группировке (не результаты агрегатов)

KIL2
22.11.2025 13:13В данном случае под агрегатами я имел ввиду результаты работы агрегатных функций SUM, COUNT и т.д.

cpud47
22.11.2025 13:13Часть про бинарные деревья не очень хорошая. Если нужно только искать и вставлять бинарные деревья никому не нужны — просто используйте хешмап.
Бинарные деревья нужны, когда нужны всякие нестандартные запросы: lower_bound, range, агрегат на отрезке и прочее.
Ну и про сложность операций не очень удачно выразились: нет никакого лучшего и худшего случая. Либо Вы работаете со сбалансированным деревом и тогда сложность
в худшем случае. Либо Вы делаете что-то сильно нетривиальное и сложность операций зависит от контекста.

SabMakc
22.11.2025 13:13Если нужно только искать и вставлять бинарные деревья никому не нужны — просто используйте хешмап.
HashMap имеет сложность вставки O(N) в худшем случае (ресайз).
Так что бинарные деревья могут быть нужны просто ради более-менее предсказуемой скорости вставки. Хотя, если честно, не сказать что это распространенная проблема - HashMap обычно более чем достаточно )
cpud47
22.11.2025 13:13Тоже об этом подумал, но потом понял что это всё ещё недостаточно причина. Можно сделать хешмап со сложностью вставки за
— нужно просто делать ресайз постепенно. Да и в целом, для большинства структур с аммортизацией можно сделать структуру без аммортизации с сохранением сложности операций: нужно просто размазать аммортизированную работу по всем операциям.
Собственно, например, в го именно так и устроен хешмап: они в момент ресайза просто создают новый массив, но не удаляют старый. Далее, при поиске они проверяют нет ли двух массивов. Если есть два массива, они мигрируют сколько-то элементов из старого в новый, после чего делают лукап в обоих. Таким образом получается честный
Такая схема сильно замедляет операции с хешмапами, конечно. Но деревья поиска прям очень медленные на самом деле. Поэтому стоит их избегать пока есть возможность.
mantiscorp
очень важный вопрос для разработчика, особенно уровня senior.
лично Вы сколько раз за свою карьеру задумывались, какой же gc используется в Вашем проекте?
нисколько?
MishaBucha Автор
Наверное как и большинство разработчиков, пока не будет с ними проблем - никто не задумывается)))
Sequoza
Можно узнать, какой примерно процент ваканский, где нужно модифицировать gc? Ну или юзкейс хотя бы. На мой взгляд, если до этого дошло, не лучше ли использовать другие языки?
MishaBucha Автор
Примерно 0 в моей практике)) обычно ты приходишь и за тебя уже все настроено, а именно выбор GC стоит очень редко, обычно дефолтный покрывает все, что нужно, за редким исключением
Akon32
Опции gc модифицировать часто приходилось, иногда менять тип gc.
Sequoza
О, отлично. Тогда не подскажите, как вы это делали? Гугл+попытки или вы точно знали что менять.
Спрашиваю потому, что большая часть моей работы и работы коллег - восставление знаний, которыми когда-то обладали или закрытие пробелов в процессе работы.
VGoudkov
Пока не покажут пальцем на GC паузы :). Сейчас то хорошо, а раньше CMS (и что было до него) иногда давал прикурить, порождая такие артефакты в кодовой базе, как самописный пул объектов.