Перейти к содержанию

Java Core

  • Java Platform: JDK, JRE, JVM, GC
  • Collection API
  • Multithreading
  • Patterns
  • Stream API

  • https://habr.com/ru/articles/485678/

  • https://javarush.com/groups/posts/2590-top-50-java-core-voprosov-i-otvetov-na-sobesedovanii-chastjh-1
  • https://javastudy.ru/interview/list-of-question-java-interview/
  • https://javastudy.ru/interview/list-of-questions-javaee-interview/

Java Особенности

  • Язык Java. Особенности языка

ООП

ООП — методология программирования, где основные конструктивные элементы — объекты, а не алгоритмы.

  • Класс — шаблон (поля + методы + конструктор)
  • Объект — экземпляр класса

Принципы ООП

Принцип Описание
Инкапсуляция Объединение данных и методов в одной «капсуле», защита от внешнего вмешательства
Наследование Описание нового класса на основе уже существующего
Полиморфизм Единообразная обработка объектов с разной реализацией через общий интерфейс

Виды полиморфизма

  • Ad hoc — перегрузка/переопределение метода
  • Параметрический — один метод работает с разными типами одинаково
  • Subtype — самый популярный: объекты с одинаковой спецификацией имеют разную реализацию

Плюсы и минусы ООП

✅ Преимущества ❌ Недостатки
Читаемый, структурированный код Объекты потребляют больше памяти
Быстрое создание сущностей Снижение производительности
Меньше дублирования кода Излишняя универсальность
Простая реализация большого функционала Сложнее начать, чем функциональное программирование

Связи между объектами

Тип Описание Пример
Ассоциация Связь между объектами Менеджер выписывает Счёт
Агрегация Один объект — часть другого; объекты равноправны Студент входит в Группу
Композиция Жёсткая зависимость: часть не может существовать без целого Двигатель внутри Машины

SOLID

Принцип Суть
S — Single Responsibility Один класс — одна ответственность
O — Open/Closed Открыт для расширения, закрыт для изменения
L — Liskov Substitution Наследник должен заменять родителя без изменения поведения
I — Interface Segregation Лучше много узких интерфейсов, чем один широкий
D — Dependency Inversion Зависимость от абстракций, не от конкретных реализаций

ООП в Java: Виды классов

Вид Описание
Обычный класс
Абстрактный класс Нельзя создать экземпляр; может содержать абстрактные методы
final класс Запрещено наследование
Вложенный (inner) Нестатический класс внутри класса; имеет доступ к полям внешнего
Статический вложенный static класс внутри класса; доступ только к статическим полям внешнего
Локальный класс Объявлен внутри метода; работает с final/effectively final переменными
Анонимный класс Локальный класс без имени; создаётся и объявляется одновременно
enum Набор логически связанных констант

Модификаторы доступа

Модификатор Класс Пакет Наследники Все
private
default
protected
public

Класс верхнего уровня может быть только public или default.

Модификаторы: static, final, abstract

static - Переменная — принадлежит классу, не объекту - Метод — нельзя переопределить (только скрыть — hiding); можно перегрузить - Класс — только вложенный; имеет доступ лишь к статике внешнего класса - Внутри static метода нельзя вызвать нестатический метод по имени класса

final - Класс — запрещено наследование (несовместим с abstract) - Метод — запрещено переопределение - Переменная-примитив — нельзя изменить значение - Переменная-ссылка — нельзя изменить ссылку, но можно изменить состояние объекта

abstract - Класс не может быть инстанциирован - Может быть без абстрактных методов - Абстрактный класс может иметь конструкторы (для наследников)

Конструкторы

Тип Описание
По умолчанию Без аргументов; генерируется компилятором, если нет других конструкторов
С параметрами Аргументы для инициализации полей
Копирования Принимает объект того же класса и создаёт его клон
  • private конструктор — запрещает создание экземпляра извне (Singleton, фабричные методы)
  • Если определён хотя бы один конструктор — дефолтный автоматически не создаётся

Интерфейсы vs Абстрактные классы

Интерфейс Абстрактный класс
Поля public static final Любые
Методы public abstract + default (Java 8+) Любые
Конструктор
Наследование Множественное (implements) Одиночное (extends)
Когда использовать Общее поведение несвязанных классов Отношение «is-a», общее состояние

Ромбовидное наследование

Java не поддерживает множественное наследование классов. Для конфликтующих default методов интерфейсов — явное разрешение:

InterfaceB.super.method();

Правила приоритета: 1. Метод класса/суперкласса > default интерфейса 2. Более специфичный интерфейс побеждает менее специфичный 3. Иначе — явное указание через super

  • Простое и множественное наследование. Особенности реализации наследования в Java
  • Понятие абстрактного класса. Модификатор abstract
  • Понятие интерфейса. Реализация интерфейсов в Java. Отличие интерфейсов от абстрактных классов

Класс Object

Базовый класс для всех классов в Java.

Метод Описание
equals(Object) Сравнение по состоянию
hashCode() Целочисленный хэш объекта
toString() Строковое представление
getClass() Тип объекта
clone() Поверхностное копирование
finalize() ⚠️ Deprecated; вызывается GC перед удалением
notify() Размораживает один случайный поток монитора
notifyAll() Размораживает все потоки монитора
wait() Поток освобождает монитор и ждёт

Методы объекта Object (wait, notify, notifyAll, hashCode, equals, clone)

equals() и hashCode()

Контракт

  1. Если hashCode разные → объекты гарантированно разные
  2. Если equalstrue, то hashCode должны быть равны
  3. Переопределил equalsобязан переопределить hashCode

Свойства equals()

  • Рефлексивность: x.equals(x) == true
  • Симметричность: a.equals(b) == b.equals(a)
  • Транзитивность: если a.equals(b) и b.equals(c)a.equals(c)
  • Постоянство: повторные вызовы возвращают одно и то же значение

Алгоритм переопределения equals()

@Override
public boolean equals(Object o) {
    if (this == o) return true;               // 1. проверка ссылок
    if (!(o instanceof MyClass)) return false; // 2. проверка типа + null
    MyClass other = (MyClass) o;              // 3. приведение типа
    return Objects.equals(field1, other.field1) && field2 == other.field2; // 4. сравнение полей
}

Правила переопределения hashCode()

  • Используй те же поля, что и в equals()
  • Повторный вызов на том же объекте → тот же результат
  • Предпочтительнее: 31 * x + y (зависимость от порядка полей), чем x + y
  • Коллизия (одинаковые hashCode у разных объектов) — допустима, но нежелательна

enum

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
  • Неявно наследует java.lang.Enum
  • Может реализовывать интерфейсы, но не наследовать классы
  • Конструктор всегда private
  • Можно создать Singleton на основе enum
Метод Описание
ordinal() Порядковый номер константы (с 0)
values() Массив всех констант
valueOf(String) Константа по имени

Плюсы над static final int: проверка типов на этапе компиляции. Минусы: нельзя использовать >, <, >=, <=; занимает больше памяти.

record

Сигнатура и передача параметров

  • Сигнатура метода = имя + параметры (порядок важен)
  • Тип возврата и исключения в сигнатуру не входят
  • Контракт метода = сигнатура + тип возврата + throws

Передача параметров

Java всегда передаёт параметры по значению!

  • Примитивы — передаётся копия значения
  • Объекты — передаётся копия ссылки (объект не копируется)

Переопределение vs Перегрузка

Переопределение (Override) Перегрузка (Overload)
Сигнатура Та же Разная (имя то же)
Возвращаемый тип Нельзя расширить, можно сузить Любой
Модификатор доступа Только расширить Любой
static методы Нельзя (только скрыть) Можно
throws Можно добавить подтипы/RuntimeException Любой

Виды ссылок в Java

Тип Поведение GC
StrongReference Обычная ссылка; GC не удалит объект
SoftReference Удаляется перед OutOfMemoryError; используется для кэша
WeakReference Удаляется при следующей сборке, если нет strong-ссылок
PhantomReference Удаляется после finalize(); get() всегда null; используется с ReferenceQueue

Кратко: Soft — пытается сохранить. Weak — не пытается. Phantom — не освобождает, пока не очищен явно.

Память: Stack и Heap

Stack Heap
Содержит Фреймы методов, локальные переменные-примитивы, ссылки Объекты
Доступ Только из текущего потока Из всей программы
Размер Меньше Больше
Скорость Быстрее Медленнее
Ошибка переполнения StackOverflowError OutOfMemoryError
  • PermGen (до Java 7) — метаданные классов, статика, пул строк
  • MetaSpace (с Java 8) — заменяет PermGen, хранится вне основной кучи
Xms — начальный размер heap
Xmx — максимальный размер heap

Типы данных

Примитивные типы

Тип Размер Описание
byte 1 байт Целое [-128; 127]
short 2 байта Целое
int 4 байта Целое
long 8 байт Целое (суффикс L)
float 4 байта Вещественное (суффикс f)
double 8 байт Вещественное (суффикс d)
char 2 байта Символ UTF-16 (беззнаковый)
boolean ~4 байта true/false

Значения по умолчанию

Тип Значение
Числовые 0 / 0L / 0.0f / 0.0d
char '\u0000'
boolean false
Объекты null

Локальные переменные в методе не имеют значений по умолчанию — только поля класса.

Приведение типов

Вид Описание
Неявное (расширение) Автоматически от меньшего к большему (intlong)
Явное (сужение) Нужно указать тип явно ((int) longValue)
  • Объекты: неявно наследник → родитель; явно родитель → наследник (иначе ClassCastException)
  • Классы-оболочки. Назначение, область применения, преимущества и недостатки. Автоупаковка и автораспаковка

Обёртки и Autoboxing

  • Обёртка — класс, хранящий значение примитива (Integer, Long, Boolean и др.)
  • Объекты-обёртки неизменяемы (Immutable)
  • Нужны для дженериков и коллекций
Термин Описание
Автоупаковка (boxing) intInteger автоматически
Автораспаковка (unboxing) Integerint автоматически

Пул Integer: значения в диапазоне [-128; 127] берутся из кэша JVM. Изменить размер кэша: -XX:AutoBoxCacheMax=<размер>

Строки

Класс Изменяемость Потокобезопасность
String Неизменяемый Да
StringBuffer Изменяемый Да (synchronized)
StringBuilder Изменяемый Нет

Пул строк (String Pool)

  • Хранится в Heap
  • При создании строки: если строка уже есть в пуле — возвращается ссылка на неё
  • new String("...") — создаёт объект вне пула
  • intern() — помещает строку в пул

Почему String неизменяемый?

  1. Реализация пула строк (интернирование)
  2. Безопасность (параметры авторизации, файлы)
  3. Потокобезопасность
  4. Кэширование hashCode() — строка — отличный ключ для HashMap

Почему не хранить пароли в String?

  • Строка остаётся в пуле до GC — уязвимость
  • Используй char[] и очищай после использования

Порядок инициализации

1. Статические блоки: от предка к наследнику
2. Динамические блоки инициализации + конструктор: попарно от предка к наследнику

Исключения в блоках инициализации: - Статический блок: явный throw → ошибка компиляции - Если исключение — наследник RuntimeException в static блоке → ExceptionInInitializerError

Исключения

Иерархия

Throwable (checked)
├── Error (unchecked) — ошибки JVM
│   ├── StackOverflowError
│   └── OutOfMemoryError
└── Exception (checked)
    ├── IOException
    ├── SQLException
    └── RuntimeException (unchecked)
        ├── NullPointerException
        ├── ClassCastException
        ├── IndexOutOfBoundsException
        └── ArithmeticException
Тип Когда обрабатывать Пример
Checked Обязательно: catch или throws IOException
Unchecked Опционально NullPointerException

Try-catch-finally

try {
    // код, где может быть исключение
} catch (IOException | SQLException e) {
    // обработка (от наследника к предку)
} finally {
    // выполняется всегда*
}

finally не выполняется при: System.exit(), Runtime.halt(), бесконечном цикле в try, фатальной ошибке JVM.

Try-with-resources (Java 7+)

try (InputStream is = new FileInputStream("file")) {
    // ресурс закрывается автоматически
}

Класс ресурса должен реализовывать AutoCloseable или Closeable. Если оба блока (try и close()) бросают исключение — побеждает исключение из try, второе подавляется (getSuppressed()).

Ключевые слова

Ключевое слово Назначение
throw Принудительно выбросить исключение
throws Объявить, что метод может выбросить исключение

Сериализация и Клонирование

Сериализация — сохранение состояния объекта в последовательность байт.

// Реализовать интерфейс-маркер
class MyClass implements Serializable {
    private static final long serialVersionUID = 1L;
    transient String password; // НЕ будет сериализован
    static int count;          // НЕ будет сериализован
}
Поле Сериализуется?
Обычное поле
transient ❌ (станет null)
static
final ✅ (кроме Externalizable)

Кастомная сериализация: реализовать Externalizable и переопределить writeExternal() / readExternal().

serialVersionUID — идентификатор версии класса. Изменять только при сознательном разрыве совместимости.

Проблема Singleton при сериализации: десериализация создаёт новый объект. Решение — добавить метод readResolve().

Интерфейс Serializable. Объектный граф, сериализация и десериализация полей и методов

Клонирование

Способ Описание
Cloneable + clone() Поверхностное копирование; clone()protected, нужно переопределить
Конструктор копирования Предпочтительный способ; явное копирование полей; работает с final
Сериализация Глубокое копирование через сериализацию/десериализацию
Поверхностное Глубокое
Примитивы Копируются Копируются
Ссылочные поля Копируется ссылка Копируется объект

Правила создания неизменяемого (Immutable) класса: - Все поля private final - Нет сеттеров - Класс объявить final - Для изменяемых полей-объектов — возвращать защитные копии

Дженерики (Generics)

Дженерики — параметризованные типы. Информация о типе доступна только при компиляции (стирание типов в runtime).

List<String> list = new ArrayList<>(); // дженерик
List list = new ArrayList();           // raw type (тип стирается → Object)

Wildcards

Синтаксис Название Можно читать Можно писать
? extends T Верхняя граница (ковариантность) ❌ (только null)
? super T Нижняя граница (контравариантность) ❌ (только Object)
<?> Неограниченный

Принцип PECS

Producer Extends, Consumer Super

  • Получаешь объекты из коллекции → extends (producer)
  • Кладёшь объекты в коллекцию → super (consumer)
  • Делаешь оба → не используй wildcard
// Producer: только читаем
void copy(List<? extends Number> src) { ... }

// Consumer: только пишем
void fill(List<? super Integer> dest) { ... }

Функциональные интерфейсы

Функциональный интерфейс — интерфейс, содержащий ровно один абстрактный метод.

  • Может иметь сколько угодно default и static методов — они не абстрактные
  • Аннотация @FunctionalInterface — не обязательна, но рекомендуется: компилятор проверит контракт и не даст добавить второй абстрактный метод
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);              // единственный абстрактный метод
    default void log() { ... }      // можно
    static void util() { ... }      // можно
}

Встроенные функциональные интерфейсы (java.util.function)

Интерфейс Сигнатура Принимает Возвращает Описание
Predicate<T> boolean test(T t) T boolean Проверка условия
Consumer<T> void accept(T t) T void Выполняет действие, ничего не возвращает
Supplier<T> T get() T Ничего не принимает, возвращает результат
Function<T,R> R apply(T t) T R Преобразует T в R
UnaryOperator<T> T apply(T t) T T Операция над одним аргументом того же типа
BinaryOperator<T> T apply(T t1, T t2) T, T T Операция над двумя аргументами того же типа
BiFunction<T,U,R> R apply(T t, U u) T, U R Два аргумента, разные типы
BiConsumer<T,U> void accept(T t, U u) T, U void Два аргумента, без результата
BiPredicate<T,U> boolean test(T t, U u) T, U boolean Проверка для двух аргументов
Mнемоника:
  Predicate  → тест (да/нет)
  Consumer   → потребляет, ничего не отдаёт
  Supplier   → только поставляет
  Function   → T → R (трансформация)
  Operator   → T → T (тот же тип)

Примитивные специализации (без boxing overhead)

Интерфейс Описание
IntPredicate, LongPredicate, DoublePredicate Predicate для примитивов
IntConsumer, LongConsumer, DoubleConsumer Consumer для примитивов
IntSupplier, LongSupplier, DoubleSupplier Supplier для примитивов
IntFunction<R>, ToIntFunction<T> Function с примитивами
IntUnaryOperator, IntBinaryOperator Операторы для int

Лямбда — упрощённая запись анонимного класса, реализующего функциональный интерфейс.

// Анонимный класс (старый стиль)
Runnable r1 = new Runnable() {
    @Override
    public void run() { System.out.println("Hello"); }
};

// Лямбда (новый стиль)
Runnable r2 = () -> System.out.println("Hello");

Синтаксис лямбды

// Базовый синтаксис: (параметры) -> тело
(параметры) -> выражение
(параметры) -> { блок; операторов; }

// Примеры:
() -> 42                              // нет параметров
x -> x * x                           // один параметр (скобки необязательны)
(x, y) -> x + y                      // два параметра
(int x, int y) -> { return x + y; }  // с явными типами и return

Пример с разными интерфейсами

Predicate<String> isEmpty   = s -> s.isEmpty();
Consumer<String>  printer   = s -> System.out.println(s);
Supplier<String>  greeting  = () -> "Hello!";
Function<String, Integer> len = s -> s.length();
UnaryOperator<String> upper = s -> s.toUpperCase();
BinaryOperator<Integer> sum = (a, b) -> a + b;

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

Ссылки на методы (Method References)

Сокращённый синтаксис лямбды, которая вызывает только один метод.

Вид Синтаксис Эквивалент лямбды
Статический метод ClassName::staticMethod (args) -> ClassName.staticMethod(args)
Метод экземпляра (конкретного) object::instanceMethod (args) -> object.instanceMethod(args)
Метод экземпляра (произвольного) ClassName::instanceMethod (obj, args) -> obj.instanceMethod(args)
Конструктор ClassName::new (args) -> new ClassName(args)
// Статический метод
Function<String, Integer> parse = Integer::parseInt;

// Метод конкретного экземпляра
String prefix = "Hello, ";
Function<String, String> greet = prefix::concat;

// Метод произвольного экземпляра
Function<String, String> upper = String::toUpperCase;

// Конструктор
Supplier<ArrayList<String>> factory = ArrayList::new;

Композиция функциональных интерфейсов

// Function: andThen (сначала f, потом g) и compose (сначала g, потом f)
Function<Integer, Integer> times2  = x -> x * 2;
Function<Integer, Integer> plus3   = x -> x + 3;

Function<Integer, Integer> times2ThenPlus3 = times2.andThen(plus3); // (x*2)+3
Function<Integer, Integer> plus3ThenTimes2 = times2.compose(plus3); // (x+3)*2

// Predicate: and, or, negate
Predicate<Integer> isPositive = x -> x > 0;
Predicate<Integer> isEven     = x -> x % 2 == 0;

Predicate<Integer> positiveAndEven = isPositive.and(isEven);
Predicate<Integer> positiveOrEven  = isPositive.or(isEven);
Predicate<Integer> isNegative      = isPositive.negate();

Замыкание (Closure) Лямбда может захватывать переменные из внешнего контекста, но только effectively final (не изменяемые после инициализации):

int multiplier = 3;               // effectively final — OK
// multiplier = 5;                // ← если раскомментировать, лямбда не скомпилируется
Function<Integer, Integer> mult = x -> x * multiplier;

Почему? Лямбда живёт дольше стека метода. Изменяемая переменная создала бы гонку данных.

Потоки ввода-вывода в Java. Байтовые и символьные потоки. "Цепочки" потоков (Stream Chains)

  • Работа с файлами в Java. Класс java.io.File
  • Пакет java.nio - назначение, основные классы и интерфейсы

Network

  • Протокол TCP. Классы Socket и ServerSocket
  • Протокол UDP. Классы DatagramSocket и DatagramPacket
  • Отличия блокирующего и неблокирующего ввода-вывода, их преимущества и недостатки. Работа с сетевыми каналами
  • Классы SocketChannel и DatagramChannel

JDBC

  • JDBC. Порядок взаимодействия с базой данных. Класс DriverManager. Интерфейс Connectio
  • Интерфейсы Statement, PreparedStatement, ResultSet, RowSe

Аннотации, рефлексия и метапрограммирование