Подготовка к Java backend интервью

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

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

Начать Java mock interview на русском Практика технического интервью на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Java и JVM

1Что такое Java и почему она считается платформонезависимой?

Java — строго типизированный язык общего назначения. Исходный код компилируется в bytecode, а bytecode выполняется JVM. Так один .class или .jar обычно можно запускать на разных ОС без перекомпиляции, если там есть совместимая JVM. Но абсолютной независимости нет, если приложение использует native libraries, platform-specific paths, system commands или особенности ОС.

Java source -> javac -> bytecode -> JVM -> machine code

2Чем JVM, JRE и JDK отличаются друг от друга?

JVM загружает и исполняет bytecode, управляет памятью, потоками и garbage collection. JRE исторически означала JVM плюс библиотеки для запуска приложений. JDK включает runtime и инструменты разработки: javac, java, debugger, javadoc, jar и diagnostic tools. В современных дистрибутивах отдельная JRE встречается реже, потому что приложения часто поставляются с runtime image.

JDK = development tools + runtime
Runtime = JVM + standard libraries

3Как JVM выполняет Java-код и зачем нужен JIT?

javac компилирует исходный код в bytecode. JVM загружает классы и начинает исполнять инструкции. Часто выполняемые участки обнаруживаются во время работы и компилируются JIT-компилятором в optimized machine code. JIT может inline методы, оптимизировать циклы, применять escape analysis и устранять некоторые аллокации. Поэтому Java-приложение может ускоряться после warm-up, а microbenchmarks без JMH часто вводят в заблуждение.

4Как работает ClassLoader?

ClassLoader загружает определения классов в JVM. Есть Bootstrap, Platform, Application и custom loaders. Класс идентифицируется не только полным именем, но и ClassLoader, который его загрузил, поэтому два класса с одинаковым именем из разных loaders могут быть разными типами. Обычно используется parent delegation: loader сначала обращается к parent, чтобы уменьшить риск подмены platform classes.

Loading
Linking: verification, preparation, resolution
Initialization

5Как JVM организует память?

Основные области JVM memory: heap для объектов и массивов, Java stacks с frames вызовов для каждого потока, Metaspace для class metadata, code cache для JIT-compiled code, PC register и native stacks. Нельзя упрощать до 'primitives всегда stack, objects всегда heap': primitive field находится внутри объекта, а JIT через escape analysis может устранить аллокацию или заменить представление объекта.

6Как работает Garbage Collector?

Garbage Collector освобождает память объектов, недостижимых из GC Roots. Roots включают локальные переменные активных методов, static fields, active threads и JNI references. Многие collectors используют гипотезу, что большинство новых объектов живет недолго, поэтому heap часто делится на young и old generations. GC не закрывает файлы и соединения детерминированно; внешние ресурсы закрывают через try-with-resources.

try (var stream = Files.newInputStream(path)) {
    // use stream
}

ООП и типы

7Какие четыре принципа ООП поддерживает Java?

Java поддерживает encapsulation, abstraction, inheritance и polymorphism. Encapsulation скрывает внутреннее состояние за public API. Abstraction выделяет существенное поведение. Inheritance создает новый тип на основе существующего. Polymorphism позволяет работать с разными реализациями через общий contract. На практике composition часто предпочтительнее inheritance, потому что дает меньшую связанность.

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}

8Чем перегрузка метода отличается от переопределения?

Overloading — несколько методов с одним именем, но разными параметрами; выбор делается компилятором по static types аргументов. Overriding — subclass заменяет реализацию instance method parent class; выбор делается runtime по фактическому типу объекта. Static methods не переопределяются, а скрываются; private methods не переопределяются, потому что не наследуются как доступные members.

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Чем interface отличается от abstract class?

Interface задает contract и может содержать abstract methods, default methods, static methods, constants и private helpers. Abstract class может дополнительно иметь instance state, constructors, protected fields, common implementation и abstract/concrete methods. Класс может extend только один class, но implement несколько interfaces. Interface лучше для независимого contract, abstract class — для близких типов с общей реализацией и состоянием.

interface Repository<T> {
    Optional<T> findById(long id);
}

10Что означает ключевое слово final?

final variable можно присвоить только один раз, но final reference не делает объект immutable. final method нельзя override. final class нельзя extend. final выражает намерение и ограничивает изменение ссылки или наследование, но сам по себе не обеспечивает полноценную immutable model или thread safety.

final int maxAttempts = 3;

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

public final void validate() {}

public final class Token {}

11Что такое record и когда его следует использовать?

Record — компактный способ объявить data carrier. Компилятор создает private final fields, accessors, constructor, equals, hashCode и toString. Records final и хорошо подходят для DTO, commands, events, calculation results, keys и values. Record дает shallow immutability: если компонент содержит mutable list, сам list все еще можно изменить, поэтому нужен defensive copy.

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

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}

12Что такое immutable object и как его создать?

Immutable object не меняет наблюдаемое состояние после создания. Обычно class делают final, fields — private final, setters не предоставляют, состояние полностью инициализируют в constructor и не раскрывают mutable internals. Для mutable inputs и outputs используют defensive copies. Immutable objects проще анализировать, безопаснее использовать как map keys и легче разделять между threads.

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 всегда передает аргументы по значению. Для primitive копируется само значение. Для object копируется значение reference. Метод может изменить объект через скопированную ссылку, но не может заменить переменную вызывающего кода. Фраза 'объекты передаются по ссылке' технически неверна: по значению передается ссылка на объект.

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

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

void replace(User user) {
    user = new User("New");
}

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

Для primitives == сравнивает values. Для objects == сравнивает identity ссылок. equals() по умолчанию в Object тоже сравнивает identity, но многие classes переопределяют equals для content equality. Если class переопределяет equals, почти всегда нужно согласованно переопределить hashCode.

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 должен быть reflexive, symmetric, transitive, consistent и возвращать false для null. Если a.equals(b) true, то hashCode должен совпадать. Обратное неверно: разные объекты могут иметь одинаковый hash. Поля, участвующие в equality/hash, нельзя менять, пока объект находится в 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 является immutable?

После создания содержимое String изменить нельзя; методы возвращают новую строку. Immutability делает безопасным String Pool, позволяет cache hash code, использовать String как HashMap key, упрощает thread safety и защищает значения вроде URLs, paths, class names и parameters. Содержимое сравнивают через equals, даже если literals могут ссылаться на один объект из pool.

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 immutable, поэтому операции могут создавать новые строки. StringBuilder mutable и не synchronized; это стандартный выбор для построения строки внутри одного thread. StringBuffer похож на StringBuilder, но synchronized, поэтому обычно медленнее и используется редко. Простая конкатенация сама по себе не проблема: compiler/runtime оптимизируют многие выражения.

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 дают type safety и убирают ручные casts. Java реализует generics в основном через type erasure: после компиляции значительная часть generic type information стирается. Поэтому нельзя new T(), new T[10] или instanceof List<String>. List<String> не является subtype List<Object>. Для гибких APIs используют wildcards и правило PECS: Producer Extends, Consumer Super.

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

List<? extends Number> producer;
List<? super Integer> consumer;

19Как устроен Java Collections Framework?

Основные interfaces: List для ordered sequence с duplicates, Set для unique elements, Queue/Deque для queues, Map для key-value pairs. Реализации выбирают по операциям: ArrayList для indexed access, HashSet для uniqueness, HashMap для key lookup, TreeMap для sorted keys, ArrayDeque для double-ended queue, ConcurrentHashMap для concurrent access.

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();

20Как работает HashMap?

HashMap использует hash ключа для выбора bucket. Затем среди элементов bucket ключ ищется через equals. Средняя сложность get/put — O(1), но collisions могут ухудшить ситуацию; современные реализации могут превращать длинную цепочку в tree при определенных условиях. HashMap допускает один null key, не гарантирует порядок и не является thread-safe.

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

User found = users.get(user.id());

21Чем HashMap, LinkedHashMap, TreeMap и ConcurrentHashMap отличаются?

HashMap дает быстрый lookup без гарантии порядка. LinkedHashMap сохраняет insertion order или access order и может быть основой LRU cache. TreeMap хранит keys sorted, операции обычно O(log n). ConcurrentHashMap рассчитан на concurrent access. Отдельные методы потокобезопасны, но последовательность нескольких calls не становится atomic автоматически.

map.computeIfAbsent(key, ignored -> loadValue());

22Чем ArrayList отличается от LinkedList?

ArrayList основан на dynamic array: indexed access O(1), append amortized O(1), insert middle O(n), хорошая cache locality. LinkedList состоит из nodes: indexed access O(n), операции на краях или через known iterator O(1), но каждый элемент требует node и references. Утверждение 'LinkedList всегда быстрее для удаления' неверно, потому что элемент часто нужно сначала найти за O(n). В большинстве прикладных случаев ArrayList быстрее, а для queue лучше ArrayDeque.

23Что такое Comparable и Comparator?

Comparable задает natural order внутри class. Comparator задает external или alternative order и удобен, когда есть несколько вариантов сортировки или class нельзя менять. Результат comparison должен быть consistent и transitive. Желательно, чтобы ordering был согласован с equals, особенно для TreeSet и TreeMap.

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);

Исключения, Stream API и многопоточность

24Чем checked exceptions отличаются от unchecked?

Checked exceptions наследуются от Exception, но не RuntimeException; compiler требует обработать или объявить их. Unchecked exceptions наследуются от RuntimeException и обычно означают contract violation, invalid state или programming error. Error описывает серьезные проблемы JVM или среды. Checked exception подходит, если caller реально может восстановиться; exceptions не должны быть обычным механизмом ветвления.

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

throw new IllegalArgumentException("Invalid id");

25Чем throw, throws и try-with-resources отличаются?

throw непосредственно выбрасывает exception. throws объявляет possible exception в method signature. try-with-resources автоматически закрывает AutoCloseable resources в reverse order. Если exception возник и в основном коде, и при close, вторичная ошибка сохраняется как suppressed exception. Это предпочтительнее ручного finally при нескольких ресурсах.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}

26Что такое Stream API?

Stream API позволяет declaratively обрабатывать sequences. Intermediate operations вроде filter/map/sorted lazy; terminal operations вроде toList/collect/reduce/forEach запускают выполнение. Stream не является collection, обычно используется один раз и лучше работает с pure functions без side effects. parallelStream не гарантирует ускорение и может ухудшить performance или correctness.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();

27Что такое Optional и когда его применять?

Optional<T> явно представляет отсутствие результата и хорошо подходит для return values. Полезные методы: map, flatMap, filter, orElse, orElseGet, orElseThrow. orElse вычисляет argument всегда, а supplier в orElseGet только если value absent. Optional обычно не рекомендуют без необходимости использовать в entity fields, method parameters и serialized DTO.

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

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

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());

28Что гарантируют synchronized, volatile и atomic-классы?

synchronized обеспечивает mutual exclusion и happens-before. volatile гарантирует visibility последних записей и ограничивает reordering, но не делает составные операции atomic. Atomic classes дают одиночные atomic operations, например incrementAndGet. Для нескольких связанных изменений используют lock/synchronized, для simple state flag — volatile, для counters — AtomicInteger или LongAdder при высокой конкуренции.

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

29Что такое race condition, deadlock и thread safety?

Race condition возникает, когда result зависит от unpredictable order выполнения threads. Deadlock — когда threads бесконечно ждут resources друг друга. Для thread safety уменьшают shared mutable state, соблюдают единый lock order, сокращают critical sections, не вызывают external code под lock, используют immutable objects, concurrent collections, timeouts и message passing. Thread safety — свойство всего protocol доступа к state.

30Чем ExecutorService, CompletableFuture и virtual threads отличаются?

ExecutorService управляет thread pool и task execution. CompletableFuture помогает compose asynchronous operations. Virtual threads — lightweight JVM threads, удобные для большого количества blocking I/O tasks. Они упрощают thread-per-request model, но не ускоряют CPU-bound work, не отменяют лимиты базы/внешних ресурсов и не делают mutable state thread-safe автоматически.

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());
}