Вопросы на собеседовании Java junior охватывают Java Core, ООП, equals и hashCode, Collections, generics, исключения, базовую многопоточность, тесты и SQL. Готовьтесь объяснять контракт и последствия выбора структуры, а не повторять определения JVM, List или Map без запускаемого примера.
Уточните используемую версию Java. Старый Oracle Tutorial прямо предупреждает, что примеры ориентированы на JDK 8, а современные руководства находятся на dev.java и в документации текущего JDK. Базовые контракты коллекций и исключений сохраняются, но синтаксис и API развиваются. Не называйте preview-функцию обычной возможностью без версии и флага компиляции. Сверяйте пример с компилятором целевого проекта и тестом.
Типы, String, class, interface, inheritance и exceptions.
List, Set, Map, Queue, generics и выбор реализации.
JVM, memory, threads, resources и диагностика.

Вопросы на собеседовании Java junior про equals и hashCode
Если объекты используются как ключи HashMap или элементы HashSet, равные по equals объекты обязаны иметь одинаковый hashCode. В record компилятор создаёт value-based equals и hashCode по компонентам. Для обычного класса контракт надо реализовать согласованно и не менять поля ключа, пока объект находится в hash-коллекции.
import java.util.HashSet;
import java.util.Set;
record UserId(long value) {}
public class Main {
public static void main(String[] args) {
Set<UserId> ids = new HashSet<>();
ids.add(new UserId(42));
ids.add(new UserId(42));
// Одинаковые значения дают один элемент множества
System.out.println(ids.size()); // 1
}
}- HashSet содержит один UserId со значением 42
- Кандидат связывает результат с equals и hashCode, а не только с Set
Следующий вопрос: почему Map не является подтипом Collection? У неё пары ключ-значение и отдельная ветка интерфейсов. Для List важен порядок и допустимы дубли, Set моделирует уникальность, Queue описывает обработку элементов, Map даёт доступ по ключу. Выбор ArrayList или LinkedList нельзя делать по названию: назовите операции, объём и фактические измерения.
Основные блоки Java junior
| Элемент | Что означает | Что делать |
|---|---|---|
| Типы и String | primitive, wrapper, immutability, pool | Предсказать == и equals |
| ООП | encapsulation, inheritance, polymorphism | Спроектировать интерфейс |
| Collections | List, Set, Map, Queue | Выбрать структуру |
| Exceptions | checked, unchecked, try-with-resources | Закрыть ресурс |
| Concurrency | thread, race, synchronized, executor | Объяснить общий доступ |
Исключение - это объект, который нарушает нормальный ход выполнения. Checked exception нужно обработать или объявить, unchecked обычно указывает на ошибку использования API или программы. try-with-resources закрывает AutoCloseable ресурс. Не ловите Exception без плана восстановления. Для многопоточности сначала объясните race condition на общем изменяемом состоянии, затем способы убрать разделение или синхронизировать доступ.

Практика: проверьте контракт equals и hashCode в HashSet
Соберите один файл Main.java и покажите на нём контракт equals и hashCode целиком. Успех: HashSet с двумя одинаковыми UserId печатает 1, тот же код на обычном классе без переопределений печатает 2, а после ручных equals и hashCode снова 1. Никаких фреймворков не нужно, хватит javac и консоли.
- record UserId в HashSet. В Main.java объявите record UserId(long value), добавьте в HashSet два раза new UserId(42) и напечатайте ids.size(). Консоль покажет 1: компилятор сгенерировал equals и hashCode по компоненту value.
- Класс без equals. Замените record обычным class UserId с полем value и конструктором, ничего не переопределяя. Тот же код напечатает 2, потому что унаследованный от Object equals сравнивает ссылки.
- Ручные equals и hashCode. Допишите в класс equals по полю value и hashCode через Objects.hash(value). Размер множества снова станет 1, и это ровно тот контракт, который record выдаёт бесплатно.
- Изменяемый ключ HashSet. Сделайте поле value изменяемым, положите объект в HashSet, поменяйте значение и вызовите ids.contains для того же объекта. Почти наверняка вернётся false: элемент лежит в корзине по старому hashCode, а ищут его по новому.
- Тест на контракт. Заверните проверки в JUnit-тест: assertEquals(1, ids.size()) и assertTrue(ids.contains(id)) до изменения поля. Зелёный тест закрепляет вывод и переживёт следующее переписывание класса.
Нарушьте контракт намеренно двумя способами. Сначала оставьте equals по value, а hashCode заставьте возвращать константу 1: множество продолжит работать правильно, но все элементы лягут в одну корзину и поиск станет линейным. Затем удалите hashCode совсем: два равных по equals UserId снова дадут size 2, потому что унаследованный hashCode считается от идентичности объекта.
Перенесите тот же разбор на HashMap: положите UserId ключом и посмотрите, что вернёт get после изменения поля. Дальше сравните HashMap с TreeMap там, где нужен порядок ключей, и с ConcurrentHashMap там, где к карте идут несколько потоков. Для каждой замены называйте операцию и объём данных, иначе ответ снова превратится в перечисление классов.
Частые ошибки и почему они появляются
Кандидат может знать названия всех коллекций, но выбрать HashMap там, где нужен отсортированный порядок или безопасный конкурентный доступ. Вторая ошибка: объяснять сборщик мусора как немедленное освобождение любого ресурса. Файлы и соединения закрывают детерминированно. Третья: заявлять потокобезопасность по одному использованию final, хотя изменяемый объект всё ещё разделяется.
Для каждой темы решите маленькую задачу и напишите тест.
Версия влияет на синтаксис, API и доступность features.
Объясняйте порядок, дубли, null, mutability и concurrency.
Самопроверка
Почему equals и hashCode переопределяют вместе?
equals и hashCode работают в паре: HashMap и HashSet сначала находят корзину по hashCode и только внутри неё сравнивают объекты через equals. Если переопределить один метод и забыть второй, равные объекты попадут в разные корзины, и коллекция будет считать их разными.
Почему Map не входит в Collection?
Map не наследует Collection, потому что хранит пары ключ-значение, а Collection описывает набор одиночных элементов. Доступ к содержимому Map дают отдельные представления: keySet, values и entrySet.
Зачем try-with-resources, если есть finally?
try-with-resources закрывает каждый AutoCloseable ресурс сам, в обратном порядке объявления, и делает это даже когда из тела вылетело исключение. Ручной finally умеет то же самое, но его легко написать с ошибкой, особенно если ресурсов несколько или закрытие само бросает исключение.
Что компилятор генерирует для record?
Для record компилятор генерирует конструктор по компонентам, методы доступа, toString, а также equals и hashCode по всем компонентам. Поэтому record удобен как ключ HashMap: контракт согласован сразу, а компоненты неизменяемы.
Что выбрать: ArrayList или LinkedList?
ArrayList и LinkedList выбирают по операциям, а не по названию: ArrayList даёт дешёвый доступ по индексу и плотное размещение в памяти, LinkedList выигрывает разве что на вставках и удалениях через уже полученный итератор. В обычных задачах ArrayList оказывается предпочтительнее, а спорный случай закрывают измерением на своих объёмах.
Освобождает ли сборщик мусора файлы и соединения?
Сборщик мусора освобождает память объектов и ничего не обещает про файл, сокет или соединение с базой. Такие ресурсы закрывают детерминированно через try-with-resources или явный close, иначе дескрипторы кончатся раньше, чем память.
Что делать дальше
Соберите 20 карточек Core, но на обратной стороне пишите не определение, а пример и тест. Последовательную практику даёт курс Java. Срок подготовки оцените по плану до junior, а общие алгоритмы потренируйте на задачах с массивами и Map.
