Вопросы на собеседовании по Java

Что спрашивают у стажёров и джунов на Java: ядро языка, коллекции, многопоточность, Spring — и три задачи, которые часто дают на live coding.

Ядро Java и коллекции

  1. Чем отличаются JDK, JRE и JVM?

    JVM исполняет байткод, JRE — JVM плюс стандартные библиотеки для запуска программ, JDK — JRE плюс инструменты разработчика: компилятор javac, отладчик и прочее.

  2. Чем == отличается от equals()?

    == для объектов сравнивает ссылки, equals() — содержимое, если метод переопределён. Для строк и своих классов в коллекциях нужен именно equals().

  3. Какой контракт у equals() и hashCode()?

    Если объекты равны по equals(), у них обязан совпадать hashCode(). Обратное не требуется. Нарушите контракт — объект «потеряется» в HashMap или HashSet.

  4. Почему String неизменяемый и когда нужен StringBuilder?

    Неизменяемость даёт безопасность, потокобезопасность и пул строк. Каждая конкатенация в цикле создаёт новый объект, поэтому для сборки строк в цикле берут StringBuilder.

  5. ArrayList или LinkedList?

    ArrayList — массив: быстрый доступ по индексу, вставка в середину дорогая. LinkedList — двусвязный список: дёшево вставлять при наличии ссылки на узел, но доступ по индексу O(n) и больше памяти. На практике почти всегда ArrayList.

  6. Как устроен HashMap?

    Массив корзин: индекс считается из hashCode() ключа. Коллизии хранятся списком, а с Java 8 длинная цепочка (от 8 элементов при достаточном размере таблицы) превращается в дерево. При заполнении выше load factor (0,75) таблица увеличивается вдвое и элементы перераспределяются.

  7. Checked и unchecked исключения — в чём разница?

    Checked (наследники Exception, кроме RuntimeException) компилятор заставляет обработать или объявить в throws. Unchecked (RuntimeException и потомки) — обычно ошибки программиста: NullPointerException, IllegalArgumentException.

  8. Интерфейс или абстрактный класс?

    Класс может реализовать много интерфейсов, но наследует только один класс. Абстрактный класс хранит состояние и конструктор; интерфейс задаёт контракт, а с Java 8 может иметь default- и static-методы.

JVM, многопоточность и Spring

  1. Как работает сборщик мусора?

    Объект собирается, когда до него нельзя добраться от корней (стек потоков, статические поля). Память делится на поколения: молодое чистится часто и быстро, старое — реже. Конкретный алгоритм зависит от сборщика, по умолчанию сейчас G1.

  2. Что делает volatile и чем он отличается от synchronized?

    volatile гарантирует видимость изменений между потоками, но не атомарность: count++ всё равно небезопасен. synchronized даёт и взаимное исключение, и видимость. Для счётчиков удобнее AtomicInteger.

  3. Как устроен Stream API?

    Промежуточные операции (filter, map) ленивые и выполняются только при терминальной (collect, count, forEach). Стрим одноразовый. Параллельные стримы ускоряют не всегда — только на больших независимых данных.

  4. Что такое внедрение зависимостей в Spring?

    Объекты-бины создаёт и связывает контейнер, а класс получает зависимости через конструктор. Так проще тестировать и подменять реализации. Скоуп бина по умолчанию — singleton.

  5. Почему @Transactional не сработал при вызове метода из того же класса?

    Транзакции в Spring работают через прокси: вызов this.method() идёт мимо прокси, и аннотация игнорируется. Решение — вынести метод в другой бин или вызывать через прокси.

  6. Зачем нужен Optional?

    Чтобы явно показать, что значения может не быть, и не возвращать null. Используют в возвращаемых значениях методов, а не в полях и параметрах. get() без проверки — плохая практика, лучше orElse, map, ifPresent.

Задачи на live coding

  1. Проверить, что скобки в строке расставлены правильно

    Стек (ArrayDeque): открывающую кладём, на закрывающую снимаем и сверяем пару. В конце стек должен быть пуст. O(n).

  2. Найти первый неповторяющийся символ

    Первый проход — посчитать частоты в LinkedHashMap или массиве, второй — найти первый символ с частотой 1.

  3. Развернуть односвязный список

    Три указателя: prev, curr, next; на каждом шаге переворачиваем ссылку curr.next = prev. O(n) по времени, O(1) по памяти.

Как вести себя на задаче и какие приёмы закрывают большинство заданий — в материале про live coding на собеседовании.