Java | Вопросы собесов
11.4K subscribers
37 photos
2 videos
1.39K links
Download Telegram
🤔 Что такое синхронизация и зачем она нужна?

Синхронизация — это механизм, который используется для контроля доступа к общим ресурсам или критическим секциям кода в среде, где несколько потоков или процессов могут одновременно выполняться. Основная цель — обеспечить корректное взаимодействие между потоками, предотвращая одновременное выполнение определённых участков кода, которые могут привести к конфликтам или неконсистентному состоянию данных.

🚩Зачем она нужна

🟠Предотвращение гонки данных (race conditions)
Гонка данных возникает, когда два или более потоков одновременно пытаются изменить общие данные, и результат выполнения зависит от того, в каком порядке потоки выполняют операции. Синхронизация помогает управлять доступом к данным таким образом, чтобы обеспечить их целостность.

🟠Обеспечение видимости изменений
В многопоточной среде изменения, сделанные одним потоком в общем ресурсе, могут не быть сразу видны другим потокам из-за кэширования данных в процессорах или оптимизаций компилятора. Синхронизация гарантирует, что изменения, сделанные одним потоком, будут корректно видны другим потокам.

🟠Последовательный доступ к ресурсам
Некоторые операции или ресурсы требуют последовательного доступа для предотвращения конфликтов или некорректной работы. Например, запись в файл или обновление базы данных должны выполняться последовательно, чтобы избежать наложения данных или повреждения структуры данных.

🚩Как она реализуется

🟠Ключевое слово synchronized
Может использоваться для блокировки целого метода или определённого блока кода, обеспечивая монопольный доступ к этому участку кода для одного потока одновременно.

🟠Явные блокировки с использованием классов из пакета java.util.concurrent.locks
Предоставляют более гибкие возможности для управления блокировками, включая попытку захвата блокировки без ожидания, захват прерываемых блокировок и блокировки с возможностью повторного входа.

🟠Волатильные переменные (volatile)
Обеспечивают видимость изменений переменных между разными потоками, но не контролируют последовательность доступа к переменной.

Ставь 👍 и забирай 📚 Базу знаний
👍3
🤔 С чем работать внутри Stream?

Внутри Stream можно работать с:
- Коллекциями (List, Set, Map) — с помощью stream() или entrySet().stream().
- Массивами — через
Arrays.stream().
- Диапазонами чисел — через IntStream.range().
- Файлами, строками, буферами — Files.lines(path) или BufferedReader.lines().
Главное — использовать ленивые промежуточные операции (map, filter, flatMap) и завершать Stream терминальной операцией (collect, forEach, reduce).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍6💊2
🤔 Что такое сигнатура?

Сигнатура метода – это его уникальная идентификация в классе. Она включает:
Имя метода
Список параметров (их типы и порядок)
class Example {
void print(String text) {} // Сигнатура: print(String)
void print(int number) {} // Сигнатура: print(int)
int print(String text, int number) { return 0; } // Сигнатура: print(String, int)
}


🚩Почему сигнатура важна?

🟠Перегрузка методов (Method Overloading)
В одном классе можно создавать методы с одинаковыми именами, но разными сигнатурами.
class MathUtils {
int sum(int a, int b) { return a + b; } // sum(int, int)
double sum(double a, double b) { return a + b; } // sum(double, double)
}


🟠Переопределение методов (Method Overriding)
При переопределении метода (в наследовании) сигнатура ДОЛЖНА быть такой же.
class Parent {
void show() {} // Сигнатура: show()
}

class Child extends Parent {
@Override
void show() {} // Сигнатура совпадает, корректное переопределение
}


🚩Ошибки, связанные с сигнатурой

Ошибка: Возвращаемый тип НЕ влияет на сигнатуру
class Test {
int method(int x) { return x; }
double method(int x) { return x; } // Ошибка! Сигнатура совпадает
}


Ставь 👍 и забирай 📚 Базу знаний
👍6
🤔 Какое ограничение есть для добавления в TreeSet?

В TreeSet элементы должны быть:
- взаимно сравнимыми — реализовывать Comparable, или должен быть передан Comparator.
- Нельзя добавить null, если используется Comparable, иначе будет NullPointerException.
Если элементы не сравнимы — будет выброшено исключение при добавлении.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🔥13👍3
🤔 Что такое as-if-serial semantics?

As-If-Serial Semantics – это принцип оптимизации компилятором, при котором код может перестраиваться, но результат его выполнения остаётся таким же, как если бы инструкции выполнялись строго по порядку.

Обычный код
int a = 10;
int b = 20;
int c = a + b;
System.out.println(c);


Что может сделать компилятор?
int c = 30;
System.out.println(c);


🚩Что можно менять? (Безопасные оптимизации)

Менять порядок инструкций, если это не влияет на результат.
Удалять лишние переменные и вычисления.
Заменять выражения константами (10 + 20 → 30).
int x = 5;
int y = 10;
x = x + 1; // x = 6
System.out.println(y);


Компилятор может поменять местами y и x
int y = 10;
int x = 6;
System.out.println(y);


🚩Что нельзя менять? (Гарантированный порядок исполнения)

int x = 10;
int y = x + 5;
x = 20;
System.out.println(y);


Если поменять порядок
x = 20;
int y = x + 5; // Неверно! y теперь 25, а должно быть 15


🚩Как `As-If-Serial` влияет на многопоточность?

В многопоточной среде компилятор может менять порядок команд внутри одного потока, но он не знает о другом потоке!
Опасный пример без volatile
boolean ready = false;
int data = 0;

void writer() {
data = 42;
ready = true;
}

void reader() {
if (ready) {
System.out.println(data); // Может напечатать 0 из-за перестановки!
}
}


Решение – volatile для ready
volatile boolean ready = false;


Ставь 👍 и забирай 📚 Базу знаний
🔥5👍3
🤔 Как остановить поток?

Остановить поток в Java можно несколькими способами, но важно помнить, что принудительная остановка потока – плохая практика. Java предлагает безопасные методы управления потоком, чтобы избежать неожиданных ошибок и некорректного поведения программы.

🚩Плохие способы (НЕ рекомендуется)

Раньше использовался метод Thread.stop(), но он был устаревшим и удалённым из-за того, что мог оставить программу в неконсистентном состоянии.
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Работаю...");
}
});

thread.start();
thread.stop(); // ОПАСНО! Может привести к некорректному завершению работы.


🚩Флаг завершения работы (рекомендуемый способ)
Самый безопасный способ – это использование флага (volatile boolean).
class MyTask implements Runnable {
private volatile boolean running = true;

public void run() {
while (running) {
System.out.println("Работаю...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Важно восстанавливать флаг прерывания
}
}
System.out.println("Поток остановлен.");
}

public void stop() {
running = false;
}
}

public class Main {
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();

Thread.sleep(2000);
task.stop(); // Корректно останавливаем поток
}
}


🚩Прерывание потока (`interrupt()`)

Этот способ удобен для потоков, которые ждут (sleep(), wait(), join()), потому что прерывание выбрасывает InterruptedException.
class MyTask implements Runnable {
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Работаю...");
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Поток прерван.");
}
}
}

public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyTask());
thread.start();

Thread.sleep(2000);
thread.interrupt(); // Прерывание потока
}
}


Ставь 👍 и забирай 📚 Базу знаний
👍5
🤔 ArrayList, какая скорость доступа к последнему элементу?

Константная. ArrayList использует массив, поэтому доступ по индексу, включая последний элемент, осуществляется очень быстро.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍11🔥3🤔1
🤔 Что такое адаптер?

Адаптер (Adapter) – это шаблон проектирования, который используется для приведения интерфейсов несовместимых классов к единому виду. Он выступает посредником между двумя несовместимыми системами.

🚩Пример: Адаптер в Java (Object Adapter)
Допустим, у нас есть старый класс OldCharger, который работает с вольтажем 220V, а мы хотим, чтобы он работал с USB (5V).
Старый интерфейс (неподходящий)
class OldCharger {
void charge220V() {
System.out.println("Зарядка 220V...");
}
}


Новый интерфейс (нужный)
interface USBCharger {
void charge5V();
}


Адаптер, который превращает 220V в 5V
class ChargerAdapter implements USBCharger {
private OldCharger oldCharger;

public ChargerAdapter(OldCharger oldCharger) {
this.oldCharger = oldCharger;
}

@Override
public void charge5V() {
System.out.println("Преобразуем 220V в 5V...");
oldCharger.charge220V();
}
}


Использование адаптера
public class Main {
public static void main(String[] args) {
OldCharger oldCharger = new OldCharger();
USBCharger adapter = new ChargerAdapter(oldCharger);

adapter.charge5V(); // Теперь старая зарядка работает с 5V!
}
}


Object Adapter (адаптер-объект) – использует композицию (пример выше).
Class Adapter (адаптер-класс) – использует наследование (extends).
class ChargerAdapter extends OldCharger implements USBCharger {
@Override
public void charge5V() {
System.out.println("Преобразуем 220V в 5V...");
charge220V();
}
}


Ставь 👍 и забирай 📚 Базу знаний
👍7
🤔 Что известно о том, как надо реализовывать equals()?

При реализации:
1. Проверить this == obj.
2. Проверить obj == null.
3. Сравнить классы.
4. Привести к нужному типу.
5. Сравнить поля (обычно те, что участвуют в hashCode).
Также нужно переопределить hashCode(), чтобы правило контрактов соблюдалось.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍4🔥3
🤔 Роль `serialVersionUID` в сериализации

Это уникальный идентификатор версии класса, который используется механизмом сериализации для проверки совместимости классов при десериализации объектов. Он играет ключевую роль в предотвращении ошибок при изменении классов, участвующих в сериализации.

🚩Почему `serialVersionUID` важен?

🟠Гарантия совместимости при десериализации
Когда объект сериализуется (превращается в поток байтов), вместе с ним сохраняется и serialVersionUID. При десериализации JVM сравнивает serialVersionUID сохранённого объекта с serialVersionUID текущего класса. Если они не совпадают, выбрасывается исключение InvalidClassException, так как структура класса могла измениться.

🟠Предотвращение ошибок при изменениях класса
Если класс изменяется (например, добавляется новое поле), но serialVersionUID остаётся неизменным, JVM считает, что класс всё ещё совместим с более старой версией, и десериализация проходит успешно.

🟠Явное управление версиями
Если serialVersionUID не указан явно, JVM генерирует его автоматически на основе структуры класса. Это может привести к неожиданным проблемам, если класс изменится, так как автоматически вычисленный serialVersionUID изменится.

🚩Как использовать `serialVersionUID`?

import java.io.*;

class Person implements Serializable {
private static final long serialVersionUID = 1L; // Версия класса
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}


🚩Что будет, если не указать `serialVersionUID`?

Если не определить serialVersionUID, JVM сгенерирует его автоматически. Однако:
Он будет зависеть от структуры класса.
Малейшее изменение в коде (даже порядок методов) изменит serialVersionUID.
Это может привести к InvalidClassException при десериализации.

Ставь 👍 и забирай 📚 Базу знаний
👍4
🤔 Что такое Request Dispatcher?

1. Request Dispatcher — это интерфейс, который позволяет передавать запрос другому ресурсу (сервлету, JSP) или включать его ответ в текущий.
2. Используется методы forward() для перенаправления и include() для включения ответа в текущий поток.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍3🔥2💊1
🤔 Какие два основных участка памяти для хранения данных есть?

Существует два главных участка памяти: стек и куча. Стек используется для хранения локальных переменных и ссылок на объекты. Он работает по принципу LIFO и очищается автоматически при выходе из метода. Куча, в свою очередь, предназначена для хранения объектов, которые создаются во время выполнения программы и могут существовать дольше, чем вызов одного метода.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍5🔥3
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое функциональный интерфейс?

Функциональный интерфейс —это интерфейс, который содержит только один абстрактный метод. Это позволяет использовать лямбда-выражения для создания его анонимных реализаций, делая код более лаконичным и читаемым. Функциональные интерфейсы являются основой для лямбда-выражений и методов ссылок, начиная с версии 8.

Примером этого может служить интерфейс java.util.function.Predicate<T> который принимает объект типа T и возвращает значение типа boolean. Вот пример использования:
Predicate<String> isNotEmpty = s -> !s.isEmpty();
System.out.println(isNotEmpty.test("Hello")); // Выведет true
System.out.println(isNotEmpty.test("")); // Выведет false


Чтобы явно указать, что интерфейс предназначен для использования как функциональный, используется аннотация @FunctionalInterface. Эта аннотация не обязательна (компилятор может определить функциональный интерфейс и без неё), но она помогает в документировании кода и обеспечивает проверку времени компиляции, гарантируя, что интерфейс содержит только один абстрактный метод.
@FunctionalInterface
public interface SimpleFunction {
int apply(int value);
}

// Использование
SimpleFunction triple = value -> value * 3;
System.out.println(triple.apply(5)); // Выведет 15


Ставь 👍 и забирай 📚 Базу знаний
👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Почему нельзя использовать byte[] в качестве ключа в HashMap?

Потому что byte[]:
- не переопределяет equals и hashCode, унаследованные от Object;
- сравнивается по ссылке, а не по содержимому;
- даже если два массива содержат одни и те же байты — equals() вернёт false.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🔥2👍1💊1