Підготовка до співбесіди з Java (Backend)

Питання та відповіді на співбесіді з Java 2026

Набір із 30 питань для співбесіди з Java для backend-розробників: від основ мови та особливостей роботи JVM до колекцій, Stream API та багатопотоковості.

Почати пробну співбесіду з JavaБанківська картка не потрібна. Доступна 1 безкоштовна сесія.
Практика технічних співбесід англійськоюРежим для тих, для кого англійська не є рідною, щоб тренувати технічні співбесіди.

Java та JVM

1Що таке Java і чому її вважають платформонезалежною?

Java — це статично типізована мова загального призначення. Вихідний код компілюється в байт-код, а байт-код виконується на JVM (Java Virtual Machine). Той самий файл .class або .jar зазвичай може працювати на різних операційних системах без перекомпіляції, якщо доступна сумісна JVM. Це не абсолютна незалежність: нативні бібліотеки, платформозалежні шляхи, системні команди та поведінка операційної системи усе одно можуть зробити застосунок платформозалежним.

Java source -> javac -> bytecode -> JVM -> machine code
Відповісти на це запитання з ШІ-тренером

2У чому різниця між JVM (Java Virtual Machine), JRE (Java Runtime Environment) та JDK (Java Development Kit)?

JVM завантажує та виконує байт-код, а також керує пам'яттю, потоками та збиранням сміття. JRE історично означало JVM плюс бібліотеки, потрібні для запуску застосунків. JDK включає середовище виконання та інструменти розробки, такі як javac, java, налагоджувач, javadoc, jar і діагностичні інструменти. У сучасних дистрибутивах окремий JRE трапляється рідше, бо застосунки часто постачають із власним образом середовища виконання.

JDK = development tools + runtime
Runtime = JVM + standard libraries
Відповісти на це запитання з ШІ-тренером

3Як JVM (Java Virtual Machine) виконує код Java і навіщо потрібна JIT (Just-In-Time compilation)?

javac компілює вихідний код у байткод. JVM завантажує класи й починає інтерпретувати або виконувати інструкції байткоду. Часто виконувані ділянки коду виявляються під час виконання й компілюються JIT-компілятором в оптимізований машинний код. JIT може виконувати підстановку методів (inlining), оптимізувати цикли, застосовувати аналіз виходу посилань (escape analysis) і прибирати частину аллокацій. Саме тому застосунки на Java можуть прискорюватися після прогріву, а мікробенчмарки без JMH часто вводять в оману.

Відповісти на це запитання з ШІ-тренером

4Як працює `ClassLoader`?

`ClassLoader` завантажує визначення класів у JVM (Java Virtual Machine). Поширеними завантажувачами є `Bootstrap`, `Platform`, `Application` та користувацькі завантажувачі. Клас ідентифікується не лише за його повністю кваліфікованим іменем (fully qualified name), але й за `ClassLoader`, який його завантажив, тому два класи з однаковим іменем від різних завантажувачів можуть бути різними типами. Java зазвичай використовує батьківське делегування: завантажувач спочатку звертається до свого батька, щоб зменшити ризик заміни класів платформи.

Loading
Linking: verification, preparation, resolution
Initialization
Відповісти на це запитання з ШІ-тренером

5Як організована пам'ять у JVM (Java Virtual Machine)?

Основні області пам'яті JVM включають купу (heap) для об'єктів і масивів, стеки Java з кадрами викликів (call frames) для кожного потоку, Metaspace для метаданих класів, кеш коду для коду, скомпільованого JIT (Just-In-Time compilation), регістр лічильника команд (PC register) та нативні стеки. Уникайте надмірного спрощення, нібито примітиви завжди зберігаються в стеку, а об'єкти — завжди в купі: примітивне поле зберігається всередині свого об'єкта, а JIT-аналіз втечі (escape analysis) може усунути виділення пам'яті або змінити представлення об'єкта.

Відповісти на це запитання з ШІ-тренером

6Як працює збирач сміття (Garbage Collector)?

Збирач сміття звільняє пам'ять, зайняту об'єктами, які більше не є досяжними з коренів GC (Garbage Collector Roots). Ці корені включають локальні змінні активних методів, статичні поля, активні потоки та посилання JNI (Java Native Interface). Багато збирачів покладаються на спостереження, що більшість нових об'єктів мають короткий час життя, тому купа (heap) часто поділяється на молоде (young generation) та старе (old generation) покоління. GC не виконує детермінованого закриття файлів або з'єднань; зовнішні ресурси слід закривати за допомогою `try-with-resources`.

try (var stream = Files.newInputStream(path)) {
    // use stream
}
Відповісти на це запитання з ШІ-тренером

ООП та типи

7Які чотири принципи OOP (Object-Oriented Programming) підтримує Java?

Java підтримує інкапсуляцію, абстракцію, успадкування та поліморфізм. Інкапсуляція приховує внутрішній стан за публічним API (Application Programming Interface). Абстракція зосереджується на суттєвій поведінці. Успадкування створює новий тип на основі існуючого. Поліморфізм дозволяє коду працювати з різними реалізаціями через спільний контракт. На практиці композиція часто є кращою за успадкування, оскільки вона створює слабкіше зв'язування.

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}
Відповісти на це запитання з ШІ-тренером

8У чому полягає різниця між перевантаженням (overloading) та перевизначенням (overriding) методів?

Перевантаження означає, що кілька методів мають однакове ім'я, але різні параметри; компілятор вибирає метод на основі статичних типів аргументів. Перевизначення означає, що підклас замінює реалізацію методу екземпляра з батьківського класу; диспетчеризація під час виконання (runtime dispatch) вибирає метод на основі фактичного типу об'єкта. Статичні методи приховуються, а не перевизначаються, а приватні методи не перевизначаються, оскільки вони не успадковуються як доступні члени.

void send(String message) {}
void send(String message, int priority) {}

class Animal {
    void speak() { System.out.println("Animal"); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("Dog"); }
}
Відповісти на це запитання з ШІ-тренером

9У чому різниця між інтерфейсом та абстрактним класом?

Інтерфейс визначає контракт і може містити абстрактні методи, методи за замовчуванням (default methods), статичні методи, константи та приватні допоміжні методи. Абстрактний клас може додатково мати стан екземпляра, конструктори, захищені поля, спільну реалізацію, а також абстрактні або конкретні методи. Клас може успадковувати лише один клас, але реалізовувати кілька інтерфейсів. Використовуйте інтерфейс для незалежного контракту, а абстрактний клас — для тісно пов'язаних типів зі спільною реалізацією та станом.

interface Repository<T> {
    Optional<T> findById(long id);
}
Відповісти на це запитання з ШІ-тренером

10Що означає ключове слово final?

Змінній `final` можна присвоїти значення лише один раз, але посилання `final` не робить об'єкт, на який воно вказує, незмінним. Метод `final` не можна перевизначити. Клас `final` не можна успадкувати. Ключове слово `final` передає намір розробника та обмежує зміни посилань або успадкування, але саме по собі воно не забезпечує повної моделі незмінності або потокобезпеки.

final int maxAttempts = 3;

final List<String> names = new ArrayList<>();
names.add("Alice"); // allowed

public final void validate() {}

public final class Token {}
Відповісти на це запитання з ШІ-тренером

11Що таке `record` у Java?

`record` — це компактний синтаксис для незмінних носіїв даних. Компілятор автоматично генерує приватні константні (`final`) поля, конструктор, методи доступу, `equals`, `hashCode` та `toString`. Вони корисні для DTO (Data Transfer Object), об'єктів-значень (value objects) та результатів виконання методів. Проте `record` не є універсальною заміною для будь-якої доменної сутності: валідацію можна додати в компактному конструкторі, але складна змінна поведінка, відкладене завантаження (lazy loading) та моделі на основі успадкування зазвичай погано підходять для `record`.

public record User(long id, String name) {}

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}
Відповісти на це запитання з ШІ-тренером

12Як створити незмінний об'єкт (immutable object) у Java?

Зробіть клас `final` або ретельно обмежте успадкування, зробіть поля `private final`, уникайте сетерів (setters) та виконуйте валідацію в конструкторі. Для змінних вхідних даних створюйте захисні копії та повертайте копії або незмінні представлення (unmodifiable views). Глибока незмінність (deep immutability) вимагає, щоб усі вкладені стани також були незмінними. Поля `final` допомагають у безпечній публікації (safe publication), але об'єкт усе ще може бути логічно змінним, якщо він надає доступ до змінної колекції або змінного вкладеного об'єкта.

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles);
    }

    public List<String> getRoles() {
        return roles;
    }
}
Відповісти на це запитання з ШІ-тренером

Посилання, рядки та порівняння

13Java передає аргументи за значенням чи за посиланням?

Java завжди передає аргументи за значенням. Для примітивів копіюється саме значення. Для об'єктів копіюється значення посилання. Метод може змінити об'єкт через скопійоване посилання, але він не може замінити посилання викликаючої сторони іншим об'єктом. Ця відмінність пояснює, чому зміна поля може бути видимою для викликаючої сторони, тоді як присвоєння параметру нового об'єкта — ні.

void change(int value) {
    value = 100;
}

void rename(User user) {
    user.setName("Kate");
}

void replace(User user) {
    user = new User("New");
}
Відповісти на це запитання з ШІ-тренером

14Яка різниця між `==` та `equals()`?

Для примітивів `==` порівнює значення. Для посилань на об'єкти `==` перевіряє, чи обидва посилання вказують на той самий об'єкт. `equals()` — це метод, який повинен порівнювати логічну рівність, якщо клас реалізує його правильно. `String`, `Integer`, `LocalDate` та багато класів значень перевизначають метод `equals`. Для користувацьких доменних об'єктів метод `equals` повинен відображати очікувану семантику ідентичності або значення.

int a = 10;
int b = 10;
System.out.println(a == b); // true

String first = new String("java");
String second = new String("java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true
Відповісти на це запитання з ШІ-тренером

15У чому полягає контракт між методами `equals` та `hashCode`?

Якщо два об'єкти рівні згідно з методом `equals`, вони повинні повертати однаковий `hashCode`. Зворотне правило не вимагається: різні об'єкти можуть мати однаковий `hashCode`. Метод `equals` має бути рефлексивним, симетричним, транзитивним, узгодженим і повертати `false` для `null`. Порушення цього контракту призводить до некоректної поведінки в `HashMap`, `HashSet` та інших колекціях, що базуються на хешуванні: об'єкт може стати неможливим для пошуку, навіть якщо він присутній у колекції.

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (!(object instanceof User other)) return false;
    return id == other.id;
}

@Override
public int hashCode() {
    return Long.hashCode(id);
}
Відповісти на це запитання з ШІ-тренером

16Чому `String` є незмінним?

`String` є незмінним заради безпеки, передбачуваності та продуктивності. Незмінність дозволяє використовувати пулювання рядків (string interning), безпечно розділяти їх між потоками, надійно кешувати `hashCode` та безпечно використовувати у шляхах, URL-адресах (Uniform Resource Locators), іменах класів і значеннях, чутливих до безпеки. Операції на кшталт `concat`, `replace` та `substring` створюють нові рядки. Починаючи з Java 9, `String` внутрішньо може використовувати `byte[]` з компактним представленням, але публічний контракт незмінності залишається тим самим.

String value = "Java";
value.toUpperCase();

System.out.println(value); // Java

String a = "java";
String b = "java";

System.out.println(a == b); // often true due to interning
Відповісти на це запитання з ШІ-тренером

17У чому різниця між `String`, `StringBuilder` та `StringBuffer`?

`String` є незмінним, тому багаторазова конкатенація в циклах може створювати багато проміжних об'єктів, хоча компілятор здатний оптимізувати прості випадки. `StringBuilder` є змінним і йому віддається перевага при формуванні рядків в одному потоці. `StringBuffer` подібний до нього, але є синхронізованим, тому він зазвичай повільніший і здебільшого актуальний для застарілих потокобезпечних API (Application Programming Interface). Для форматування `String.format` або API у стилі шаблонів можуть бути більш зрозумілими, але вони не завжди працюють швидше.

StringBuilder builder = new StringBuilder();

for (String item : items) {
    builder.append(item).append(',');
}

String result = builder.toString();

String message = "Hello, " + name;
Відповісти на це запитання з ШІ-тренером

Узагальнення (generics) та колекції

18Що таке узагальнення (generics) і що таке стирання типів (type erasure)?

Узагальнення (generics) дозволяють коду виражати безпечні щодо типів контейнери та API (Application Programming Interfaces), наприклад List<String>. Під час компіляції компілятор перевіряє типи та вставляє приведення типів (casts), де це необхідно. Під час виконання більшість інформації про узагальнені типи видаляється, тому List<String> та List<Integer> використовують один і той самий клас під час виконання. Саме тому ви не можете безпосередньо створити new T(), використовувати примітивні параметри типів або надійно перевірити instanceof List<String>.

List<String> names = new ArrayList<>();
names.add("Alice");

List<? extends Number> producer;
List<? super Integer> consumer;
Відповісти на це запитання з ШІ-тренером

19Що таке Java Collections Framework?

Collections Framework — це набір інтерфейсів та їх реалізацій для зберігання та обробки груп об'єктів. Основні інтерфейси включають `List`, `Set`, `Queue`, `Deque` та `Map`. Реалізації включають `ArrayList`, `LinkedList`, `HashSet`, `TreeSet`, `HashMap`, `TreeMap` та `PriorityQueue`. Колекцію слід вибирати з огляду на спосіб доступу (access pattern), вимоги до впорядкування, унікальність елементів, складність пошуку, накладні витрати пам'яті та потреби в конкурентному (concurrency) доступі.

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();
Відповісти на це запитання з ШІ-тренером

20Як працює HashMap?

`HashMap` зберігає записи (entries) у корзинах (buckets), які обираються за допомогою хешу ключа. Для пошуку обчислюється хеш, знаходиться відповідна корзина, і ключі порівнюються за допомогою методу `equals`. Колізії обробляються всередині корзини; у сучасній Java довгі ланцюжки можуть бути перетворені на збалансоване дерево (treeified) за певних умов. `HashMap` допускає один ключ `null` та значення `null`, не є потокобезпечним (thread-safe) і може змінювати розмір (resize), коли перевищується поріг коефіцієнта завантаження (load factor).

Map<Long, User> users = new HashMap<>();
users.put(user.id(), user);

User found = users.get(user.id());
Відповісти на це запитання з ШІ-тренером

21У чому різниця між `HashMap`, `LinkedHashMap`, `TreeMap` та `ConcurrentHashMap`?

`HashMap` забезпечує швидкий доступ без збереження порядку та не є потокобезпечним. `LinkedHashMap` зберігає порядок вставлення або доступу і є корисним для кешів типу LRU (Least Recently Used). `TreeMap` зберігає ключі відсортованими за природним порядком або за допомогою `Comparator` і виконує операції за час O(log n). `ConcurrentHashMap` призначений для конкурентного доступу з сегментованою або внутрішньою синхронізацією, не дозволяє використовувати `null` у якості ключів або значень, і є кращим вибором порівняно з `synchronizedMap` під навантаженням.

map.computeIfAbsent(key, ignored -> loadValue());
Відповісти на це запитання з ШІ-тренером

22У чому різниця між `ArrayList` та `LinkedList`?

В основі `ArrayList` лежить масив. Він забезпечує швидкий доступ за індексом, компактне розташування в пам'яті та ефективну ітерацію, але вставлення в середину вимагає зсуву елементів. `LinkedList` — це двозв'язний список. Вставлення в нього дешевше, якщо вузол уже відомий, але доступ за індексом займає O(n) часу, витрати пам'яті вищі, а локальність кешу (cache locality) гірша. У багатьох реальних застосунках `ArrayList` працює швидше, навіть якщо присутні операції вставлення.

Відповісти на це запитання з ШІ-тренером

23У чому різниця між `Comparable` та `Comparator`?

`Comparable` визначає природний порядок всередині класу за допомогою методу `compareTo`. `Comparator` — це окремий об'єкт або лямбда-вираз, який визначає зовнішній порядок. `Comparable` корисний, коли тип має один очевидний порядок за замовчуванням. `Comparator` краще підходить, коли існує кілька варіантів сортування, наприклад, за ім'ям, датою або пріоритетом. Методи `compare` та `compareTo` мають бути узгодженими і, бажано, сумісними з `equals`, якщо вони використовуються у відсортованих множинах (sorted sets) або словниках (maps).

class User implements Comparable<User> {
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(age, other.age);
    }
}

Comparator<User> byName =
    Comparator.comparing(User::getName);

Comparator<User> byAgeThenName =
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName);
Відповісти на це запитання з ШІ-тренером

Винятки та ресурси

24У чому різниця між винятками, що перевіряються (checked exceptions), та тими, що не перевіряються (unchecked exceptions)?

Винятки, що перевіряються (checked exceptions), успадковуються від `Exception`, але не від `RuntimeException`, і компілятор вимагає від коду, що їх викликає, обробити їх або оголосити. Винятки, що не перевіряються (unchecked exceptions), успадковуються від `RuntimeException` і не вимагають оголошення в сигнатурах методів. Винятки, що перевіряються, підходять для ситуацій на межі API (Application Programming Interface), після яких можна відновити роботу. Винятки, що не перевіряються, підходять для помилок програмування, недійсного стану та порушень контрактів. У сучасному коді сервісів команди часто уникають надмірного використання винятків, що перевіряються, оскільки вони створюють багато "шуму", поширюючись через різні рівні абстракції.

void load() throws IOException {
    // ...
}

throw new IllegalArgumentException("Invalid id");
Відповісти на це запитання з ШІ-тренером

25У чому різниця між `throw`, `throws` та конструкцією `try-with-resources`?

Ключове слово `throw` негайно генерує виняток. Ключове слово `throws` оголошує можливий виняток у сигнатурі методу. Конструкція `try-with-resources` автоматично закриває ресурси, що реалізують інтерфейс `AutoCloseable`, у зворотному порядку. Якщо і основний блок, і метод `close` генерують винятки, вторинний виняток зберігається як пригнічений (suppressed exception). Це безпечніше та зрозуміліше, ніж ручне використання блоків `finally`, коли задіяно кілька ресурсів.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}
Відповісти на це запитання з ШІ-тренером

Stream API та функціональний стиль

26Що таке Stream API?

Stream API (Application Programming Interface для потоків) дозволяє обробляти послідовності в декларативному стилі. Проміжні операції (intermediate operations), такі як `filter`, `map` та `sorted`, є лінивими; термінальні операції (terminal operations), такі як `toList`, `collect`, `reduce` та `forEach`, запускають виконання. Потік не є колекцією, зазвичай використовується лише один раз і найкраще працює з чистими функціями без побічних ефектів. Використання `parallelStream` не гарантує швидшого виконання і може погіршити продуктивність або правильність роботи програми.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();
Відповісти на це запитання з ШІ-тренером

27Що таке `Optional` і коли його слід використовувати?

`Optional<T>` явно представляє відсутність результату і добре підходить у якості типу повернення. До корисних методів належать `map`, `flatMap`, `filter`, `orElse`, `orElseGet` та `orElseThrow`. Метод `orElse` обчислює свій аргумент невідкладно (eagerly), тоді як постачальник (supplier) у `orElseGet` виконується лише за відсутності значення. Зазвичай не рекомендується використовувати `Optional` для полів сутностей, параметрів методів або серіалізованих DTO (Data Transfer Object), якщо для цього немає вагомих причин.

Optional<User> user = repository.findById(id);

String name = user
    .map(User::getName)
    .orElse("Unknown");

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());
Відповісти на це запитання з ШІ-тренером

Багатопотоковість

28Що гарантують ключові слова `synchronized`, `volatile` та атомарні класи (atomic classes)?

Ключове слово `synchronized` забезпечує взаємне виключення (mutual exclusion) та відношення "передує" (happens-before). Ключове слово `volatile` гарантує видимість останніх записів і обмежує перевпорядкування інструкцій (reordering), але не робить складені операції атомарними. Атомарні класи надають окремі атомарні операції, такі як `incrementAndGet`. Використовуйте блокування (locks) або `synchronized` для кількох пов'язаних змін, `volatile` — для простих прапорців стану, а `AtomicInteger` або `LongAdder` — для лічильників в умовах високої конкуренції потоків.

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
Відповісти на це запитання з ШІ-тренером

29Що таке стан гонитви (race condition), взаємне блокування (deadlock) та потокобезпечність (thread safety)?

Стан гонитви (race condition) виникає, коли результат залежить від непередбачуваного порядку виконання потоків. Взаємне блокування (deadlock) стається, коли потоки нескінченно чекають на ресурси одне одного. Щоб досягти потокобезпечності, зменшуйте кількість спільного змінного стану (shared mutable state), підтримуйте узгоджений порядок блокувань, скорочуйте критичні секції, уникайте викликів зовнішнього коду під час утримання блокувань, а також використовуйте незмінні (immutable) об'єкти, конкурентні колекції, тайм-аути або передавання повідомлень. Потокобезпечність — це властивість усього протоколу доступу до стану.

Відповісти на це запитання з ШІ-тренером

30У чому різниця між `ExecutorService`, `CompletableFuture` та віртуальними потоками (virtual threads)?

`ExecutorService` керує пулом потоків та виконанням завдань. `CompletableFuture` допомагає компонувати асинхронні операції. Віртуальні потоки — це легковагові потоки JVM (Java Virtual Machine), які є зручними для великої кількості задач з блокувальним вводом/виводом (I/O). Вони спрощують модель "один потік на запит" (thread-per-request), але не пришвидшують роботу, що інтенсивно використовує процесор (CPU-bound), не знімають обмеження баз даних або зовнішніх ресурсів, і не роблять змінний стан потокобезпечним автоматично.

try (var executor = Executors.newFixedThreadPool(4)) {
    Future<Integer> future = executor.submit(() -> calculate());
    int result = future.get();
}

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(this::loadUser)
        .thenApply(this::enrichUser)
        .exceptionally(error -> fallbackUser());

try (var executor =
         Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}
Відповісти на це запитання з ШІ-тренером