Вопросы по Java Core, многопоточности, JVM, Garbage Collection и Spring Framework.
ArrayList реализован на основе динамического массива, а LinkedList — на основе двусвязного списка. * **Сложность доступа**: ArrayList осуществляет доступ к элементу за `O(1)`, так как обращается напрямую по индексу. LinkedList требует последовательного перебора элементов от начала или конца списка до нужной позиции за `O(N)`. * **Сложность вставки/удаления**: ArrayList при вставке в середину или начало требует сдвига всех последующих элементов вправо, что занимает `O(N)`. При заполнении базового массива происходит его перевыделение и копирование за `O(N)`. LinkedList добавляет или удаляет узлы за `O(1)`, перетирая указатели у соседних элементов, но предварительно нужно найти этот элемент за `O(N)`. * **Расход памяти**: LinkedList тратит больше памяти, так как на каждый элемент создается объект узла с двумя дополнительными ссылками на предыдущий и следующий элементы.
HashMap хранит пары «ключ-значение» и построен на основе хэш-таблицы (массива бакетов). 1. **Получение бакета**: Вычисляется хэш-код ключа (`key.hashCode()`), затем он сжимается по размеру массива бакетов с помощью побитовых операций. 2. **Коллизии**: Если несколько ключей попадают в один бакет, они организуются в связанный список (Singly Linked List). Начиная с Java 8, если длина связанного списка в бакете превышает 8 элементов (и общий размер таблицы не менее 64), список преобразуется в сбалансированное красно-черное дерево (Red-Black Tree) для оптимизации поиска. 3. **Сложность**: В среднем случае сложность операций `get` и `put` составляет `O(1)`. В худшем случае (когда все элементы попадают в один бакет и дерево не может быть создано) — `O(N)`. Для сбалансированного дерева худший случай — `O(log N)`.
String Pool (пул строк) — это область памяти в куче (Heap), предназначенная для хранения уникальных литералов строк. Это оптимизация для экономии памяти, так как строки в Java неизменяемы (immutable). * **Создание строк**: Строка, созданная литералом (например, `String s1 = "hello"`), сохраняется в String Pool. Если строка с таким значением уже есть, Java просто возвращает ссылку на нее. Строка, созданная через `new String("hello")`, всегда создает новый объект в куче, обходя пул строк. * **Метод `intern()`**: При вызове `s1.intern()` JVM ищет в String Pool строку с аналогичным значением. Если она найдена, возвращается ссылка на нее из пула. Если нет — текущая строка добавляется в пул строк, и возвращается ссылка на этот новый элемент пула.
Абстрактный класс задает логику наследования («is-a»), интерфейс — контракт поведения («can-do»). * **Множественное наследование**: Класс может наследоваться только от одного абстрактного класса, но может реализовывать любое количество интерфейсов. * **Поля**: В абстрактном классе поля могут иметь любые модификаторы доступа (private, protected, public) и быть нефинальными. В интерфейсах все переменные по умолчанию являются `public static final` (константы). * **Методы**: Абстрактные классы могут иметь конструкторы, а также любые методы с реализацией и без. Интерфейсы не имеют конструкторов. Начиная с Java 8 в них разрешены методы по умолчанию (`default`) и статические методы с телом, а с Java 9 — приватные методы.
Оба класса используются для работы с изменяемыми строками без постоянного создания новых объектов, но различаются потокобезопасностью: * **StringBuffer**: Потокобезопасен. Большинство его методов синхронизированы (`synchronized`), что делает его безопасным для использования в многопоточной среде. * **StringBuilder**: Не потокобезопасен. Его методы не синхронизированы. За счет этого он работает быстрее, чем StringBuffer, и является рекомендуемым выбором для однопоточного использования.
Garbage Collector (сборщик мусора) автоматически освобождает память в куче (Heap), удаляя объекты, на которые больше нет активных ссылок. * **Модель поколений (Generational Hypothesis)**: Память делится на Young Generation (для новых короткоживущих объектов) и Old Generation (для долгоживущих объектов). * **Основные алгоритмы**: 1. **Serial GC**: Простой однопоточный сборщик для небольших приложений. Останавливает все потоки (Stop-The-World). 2. **Parallel GC (Throughput Collector)**: Многопоточный сборщик для Young и Old поколений. Оптимизирован под высокую пропускную способность. 3. **G1 (Garbage First) GC**: Разделяет кучу на множество равных регионов. Собирает мусор в регионах с наибольшим его количеством, минимизируя паузы Stop-The-World. 4. **ZGC / Shenandoah**: Современные сборщики с ультранизкими паузами (менее 1 мс), выполняющие сборку мусора параллельно с работой приложения.
Это механизмы синхронизации в Java для управления конкурентным доступом: * **`volatile`**: Гарантирует видимость изменений переменной между потоками. Чтение и запись volatile-переменной происходят напрямую в основную память приложения, минуя кэши процессоров. Не обеспечивает атомарность (например, операция `count++` не будет атомарной). * **`synchronized`**: Обеспечивает взаимное исключение (mutual exclusion). Только один поток может выполнять synchronized-блок или метод на определенном мониторе. Принудительно синхронизирует кэши процессоров с общей памятью. * **`ReentrantLock`**: Альтернатива synchronized из пакета `java.util.concurrent.locks`. Предоставляет гибкие возможности: опрос блокировки с таймаутом (`tryLock()`), честную очередь ожидания (fairness), прерываемую блокировку и возможность привязки нескольких условий (`Condition`).
* **`final`**: Модификатор. Класс с final нельзя наследовать; метод с final нельзя переопределять; переменную с final можно инициализировать только один раз, после чего она становится константой. * **`finally`**: Блок в конструкции `try-catch-finally`. Выполняется в любом случае при выходе из try/catch блока (за исключением вызова `System.exit(0)` или сбоя JVM). * **`finalize()`**: Метод класса `java.lang.Object`. Вызывается сборщиком мусора перед удалением объекта. Начиная с Java 9, метод помечен как `@Deprecated`, так как его вызов не гарантирован и он может вызывать утечки памяти. Вместо него рекомендуется использовать `AutoCloseable` и try-with-resources.
ClassLoader (загрузчик классов) — это часть JRE, которая динамически загружает файлы классов (`.class`) в память JVM при первом обращении к ним. * **Основные принципы работы**: 1. **Делегирование (Delegation)**: Запрос на загрузку класса передается родительскому загрузчику. Класс загружается дочерним только в том случае, если все родительские загрузчики не смогли его найти. 2. **Видимость (Visibility)**: Дочерний загрузчик видит классы, загруженные родительским, но родительский не видит классы, загруженные дочерним. 3. **Уникальность (Uniqueness)**: Класс загружается ровно один раз в рамках одного загрузчика. * **Иерархия**: Bootstrap ClassLoader (базовые классы платформы) -> Platform/Extension ClassLoader -> Application/System ClassLoader -> Custom ClassLoaders.
Все исключения наследуются от класса `Throwable`. * **Checked (проверяемые) исключения**: Наследуются напрямую от `Exception` (кроме `RuntimeException`). JVM обязывает разработчика обработать их в блоке `try-catch` или объявить в сигнатуре метода с помощью `throws`. Пример: `IOException`, `SQLException`. * **Unchecked (непроверяемые) исключения**: Наследуются от `RuntimeException` или `Error`. Их обработка не проверяется компилятором на этапе сборки. Обычно они указывают на ошибки в логике программы. Пример: `NullPointerException`, `ArrayIndexOutOfBoundsException`.
SOLID — это пять базовых принципов проектирования ПО: * **S (Single Responsibility)**: Принцип единственной ответственности. У класса должна быть только одна причина для изменения. * **O (Open/Closed)**: Принцип открытости/закрытости. Классы должны быть открыты для расширения, но закрыты для модификации. * **L (Liskov Substitution)**: Принцип подстановки Барбары Лисков. Функции, использующие базовый тип, должны иметь возможность использовать подтипы базового типа без ведома об этом. * **I (Interface Segregation)**: Принцип разделения интерфейса. Много специализированных интерфейсов лучше, чем один универсальный. * **D (Dependency Inversion)**: Принцип инверсии зависимостей. Зависимости внутри системы должны строиться на основе абстракций, а не конкретных реализаций.
* **Fail-fast итераторы**: При изменении коллекции во время итерирования (кроме методов самого итератора) немедленно генерируют `ConcurrentModificationException`. Работают напрямую на исходной коллекции. Пример: итераторы классов `ArrayList`, `HashMap`. * **Fail-safe (или weakly consistent) итераторы**: Не генерируют исключение при модификации. Они работают на копии коллекции или создают слабо-согласованный снимок данных на момент создания итератора. Пример: итераторы потокобезопасных коллекций вроде `CopyOnWriteArrayList`, `ConcurrentHashMap`.
* **IoC (Inversion of Control)**: Принцип проектирования, при котором управление жизненным циклом объектов передается фреймворку, а не пишется разработчиком вручную. * **IoC Container**: Компонент Spring, который создает объекты (бины), конфигурирует их, связывает зависимости друг с другом и управляет их жизненным циклом от создания до уничтожения. * **Dependency Injection (DI)**: Паттерн реализации IoC, при котором зависимости передаются объекту извне (через конструктор, сеттер или напрямую в поле), вместо того чтобы объект создавал их сам через `new`.
Spring поддерживает следующие области видимости бинов: * **singleton** (по умолчанию): Создается один экземпляр бина на весь IoC контейнер Spring. * **prototype**: Создается новый экземпляр бина при каждом запросе из контекста. * **request**: Один экземпляр на каждый HTTP-запрос (только в Web-приложениях). * **session**: Один экземпляр на каждую HTTP-сессию. * **application**: Один экземпляр на жизненный цикл `ServletContext`. * **websocket**: Один экземпляр на жизненный цикл WebSocket сессии.
* **BeanFactory**: Базовый интерфейс контейнера Spring. Предоставляет ленивую инициализацию бинов (создает объект только при обращении к нему через `getBean()`), потребляет меньше памяти. * **ApplicationContext**: Наследует `BeanFactory` и расширяет его. Поддерживает энергичную (eager) инициализацию синглтон-бинов при старте приложения, предоставляет встроенную поддержку интернационализации (I18N), интеграцию с AOP, публикацию событий (`ApplicationEventPublisher`) и поддержку WebApplicationContext.
Аннотация `@Transactional` в Spring реализована с помощью паттерна **Proxy (AOP)**. 1. Когда Spring создает бин, класс которого или методы которого помечены `@Transactional`, он оборачивает этот бин в прокси-класс (через Dynamic Proxies JDK или CGLIB). 2. При вызове транзакционного метода прокси перехватывает вызов, открывает транзакцию в базе данных (через `PlatformTransactionManager`). 3. Затем вызывается реальный целевой метод. 4. Если метод завершился успешно, прокси выполняет `commit` транзакции. Если метод выбросил `RuntimeException` (по умолчанию проверяемые исключения транзакцию не откатывают, если это не переопределено в параметре `rollbackFor`), прокси выполняет `rollback` транзакции.
Spring Framework — это мощная инфраструктурная экосистема, которая требует обширной ручной конфигурации (XML или Java-конфиг, настройка DispatcherServlet, Tomcat и т.д.). Spring Boot — это расширение над Spring, направленное на ускорение старта разработки: * **Автоконфигурация (Auto-configuration)**: Автоматически настраивает компоненты Spring на основе библиотек в classpath (стартеров). * **Стартовые зависимости (Starters)**: Набор преднастроенных зависимостей для сборщиков проектов (например, `spring-boot-starter-web`). * **Встроенный сервер**: Встроенные контейнеры серверов (Tomcat, Jetty, Undertow) позволяют запускать приложение как исполняемый JAR-файл без внешнего сервера приложений.
* **JPA (Java Persistence API)**: Это спецификация (набор интерфейсов и стандартов), описывающая объектно-реляционное отображение (ORM) в Java. JPA не содержит исполняемого кода. * **Hibernate**: Это популярная библиотека-реализация (провайдер) спецификации JPA. Помимо стандартных возможностей JPA, Hibernate предоставляет свои собственные расширенные возможности (например, специфические типы данных, оптимизации кэширования, методы работы с графами объектов).
* **Кэш первого уровня (First-level cache)**: Кэш сессии (`Session` или `EntityManager`). Включен по умолчанию и привязан к текущей транзакции. Все сущности, загруженные в рамках одной сессии, сохраняются в ней. Кэш очищается при закрытии сессии. * **Кэш второго уровня (Second-level cache)**: Кэш фабрики сессий (`SessionFactory`). Отключен по умолчанию. Является разделяемым между всеми сессиями приложения. Требует интеграции внешних провайдеров кэширования (Ehcache, Hazelcast, Redis).
Проблема N+1 возникает, когда при запросе списка из N сущностей, связанных с другими сущностями связью One-To-Many или Many-To-One, Hibernate выполняет 1 запрос для получения родительских записей и еще N запросов для получения дочерних записей. **Способы решения**: 1. **Fetch Join**: Использование `JOIN FETCH` в JPQL или HQL запросах для загрузки связей одним SQL-запросом. 2. **Entity Graph**: Указание графа сущностей через аннотацию `@EntityGraph` для явного перечисления загружаемых связей. 3. **@BatchSize**: Указание размера батча загрузки дочерних сущностей (выполняется несколько сгруппированных запросов вместо N запросов). 4. **FetchType.LAZY**: Делать связи ленивыми по умолчанию, но при выборке использовать Fetch Join.
Функциональный интерфейс — это интерфейс, содержащий ровно один абстрактный метод (но может содержать любое количество default и static методов). Аннотируется `@FunctionalInterface`. **Основные типы в JDK**: * **`Predicate<T>`**: Принимает объект `T`, возвращает `boolean` (метод `test(T t)`). * **`Consumer<T>`**: Принимает объект `T`, выполняет действие, ничего не возвращает (метод `accept(T t)`). * **`Supplier<T>`**: Не принимает аргументов, поставляет объект типа `T` (метод `get()`). * **`Function<T, R>`**: Принимает объект `T`, преобразует и возвращает объект типа `R` (метод `apply(T t)`). * **`UnaryOperator<T>`**: Принимает и возвращает объект одного типа `T` (расширяет `Function`).
Stream API предоставляет декларативный способ обработки коллекций данных. * **Промежуточные (Intermediate) операции**: Возвращают новый поток. Они ленивы (lazy) — не выполняются до вызова терминальной операции, а только строят конвейер обработки. Пример: `filter()`, `map()`, `flatMap()`, `sorted()`, `distinct()`. * **Терминальные (Terminal) операции**: Запускают обработку данных по конвейеру и закрывают поток. Возвращают конкретный результат (коллекцию, число, логическое значение) или ничего (`void`). Пример: `collect()`, `forEach()`, `reduce()`, `count()`, `findFirst()`, `anyMatch()`.
* **`Thread`**: Класс в Java, представляющий собой поток выполнения на уровне операционной системы. * **`Runnable`**: Функциональный интерфейс с единственным методом `run()`, описывающий задачу, которая должна быть выполнена. * **Разница**: Наследование от `Thread` ограничивает класс в возможности наследоваться от других классов (из-за отсутствия множественного наследования). Реализация `Runnable` более гибка, позволяет разделять одну задачу между несколькими потоками и использовать пулы потоков (Executors Framework).
`ThreadLocal` предоставляет переменные, для каждого потока имеющие свой собственный независимый экземпляр. Каждый поток работает со своей локальной копией переменной. **Применение**: * Хранение контекста авторизованного пользователя (например, `SecurityContext` в Spring Security). * Хранение соединений с базой данных на время транзакции. * Хранение непотокобезопасных утилит (например, `SimpleDateFormat`) без надобности синхронизации.
* **`Future`**: Появился в Java 5. Представляет собой результат асинхронного вычисления. Главный минус — чтобы получить результат, нужно вызывать блокирующий метод `get()` или опрашивать готовность в цикле (`isDone()`). * **`CompletableFuture`**: Появился в Java 8. Позволяет строить неблокирующие цепочки обработки асинхронных задач (в стиле Promise в JavaScript) с помощью колбэков: `thenApply()`, `thenAccept()`, `thenCombine()`. Также поддерживает асинхронную обработку ошибок через `exceptionally()`.
* **`Thread.sleep()`**: Статический метод класса `Thread`. Приостанавливает выполнение текущего потока на время, **не освобождая** блокировки (мониторы), которыми владеет поток. * **`Object.wait()`**: Метод класса `Object`. Вызывается только из синхронизированного контекста. Поток временно приостанавливает работу и **освобождает** монитор объекта, позволяя другим потокам войти в синхронизированный блок. Поток просыпается при вызове `notify()` или `notifyAll()` на этом же объекте.
* **Пессимистическая блокировка**: Блокирует запись (или чтение) в базе данных на все время транзакции (например, `SELECT ... FOR UPDATE`). Никто другой не может изменить запись, пока транзакция не завершится. Используется при высокой вероятности конфликтов. * **Оптимистическая блокировка**: Не блокирует данные на уровне СУБД. Сущность содержит поле версии или таймштампа. При записи проверяется, не изменилась ли версия в БД. Если версия изменилась, транзакция откатывается и генерируется исключение (например, `OptimisticLockException` в JPA). Используется при редких конфликтах.
Reflection API (рефлексия) — это механизм получения метаданных о классах, методах, полях во время выполнения приложения, а также управления ими. * **Возможности**: Создание объектов неизвестных классов, вызов приватных методов, изменение приватных полей. * **Применение**: Фреймворки (Spring для внедрения зависимостей, Hibernate для маппинга сущностей, библиотеки сериализации Jackson/Gson). * **Минусы**: Снижает производительность, обходит инкапсуляцию, лишает компилятор возможности статической проверки типов.
* **`equals()`**: Сравнивает объекты на логическое равенство. * **`hashCode()`**: Возвращает числовое представление объекта (хэш-код) для быстрой группировки в хэш-таблицах. * **Контракт**: 1. Если два объекта равны по `equals()`, их `hashCode()` обязательно должны возвращать одинаковые значения. 2. Если у двух объектов одинаковый `hashCode()`, они не обязательно равны по `equals()` (ситуация коллизии). 3. При переопределении одного метода необходимо обязательно переопределять второй.
Виртуальные потоки (проект Loom) — это легковесные потоки, которые управляются самой JVM, а не операционной системой напрямую. * **Масштабируемость**: Стандартные платформенные потоки дороги в создании и маппятся 1:1 на потоки ОС (ограничение в несколько тысяч потоков). Виртуальные потоки дешевы, их можно создавать миллионами. * **Модель программирования**: Позволяют писать синхронный блокирующий код (thread-per-request), который под капотом выполняется асинхронно. Когда виртуальный поток блокируется на операции ввода-вывода (I/O), JVM переключает нижележащий поток ОС на выполнение другого виртуального потока, не блокируя системный ресурс.
Java Memory Model (JMM) определяет правила взаимодействия потоков с памятью и кэшами. Из-за оптимизаций компилятора и процессора порядок инструкций может меняться, а потоки могут кешировать переменные локально. Правила **happens-before** гарантируют, что результаты записи в память одним потоком будут видны другому потоку: * **Volatile write/read**: Запись в volatile-переменную happens-before чтению той же переменной. * **Thread start/join**: Вызов `Thread.start()` happens-before первому действию в потоке; завершение работы потока happens-before возврату из `Thread.join()`. * **Monitor lock**: Освобождение блокировки (выход из synchronized) happens-before захвату той же блокировки другим потоком. * **Транзитивность**: Если A happens-before B, а B happens-before C, то A happens-before C.
* **ThreadPoolExecutor**: Предназначен для независимых, параллельно исполняемых задач. Каждый рабочий поток забирает задачи из общей блокирующей очереди (BlockingQueue). Потоки не общаются друг с другом. * **ForkJoinPool**: Разработан для задач, которые можно рекурсивно разбивать на подзадачи (принцип разделяй и властвуй). Использует алгоритм **Work-Stealing** (кража работы): каждый поток имеет свою собственную двустороннюю очередь (deque). Если поток закончил свои задачи, он крадет задачи из конца очереди других потоков, что минимизирует простои процессора.
G1 GC делит кучу (Heap) на множество равных регионов (обычно от 2048 размером от 1 до 32 МБ). Каждый регион динамически назначается для Eden, Survivor или Old поколения. * **Garbage First**: Сборщик собирает мусор в регионах, где больше всего «мусора» (неиспользуемых объектов), чтобы освободить максимум памяти за минимальное время. * **Card Table & Write Barrier**: Чтобы избежать полного сканирования Old поколений при сборке Young поколений, куча делится на виртуальные карты (cards) размером 512 байт. Запись ссылки из Old в Young отслеживается с помощью **Write Barrier** и фиксируется в **Card Table** (или Remembered Sets).
В Java любой объект имеет накладные расходы (оверхед): 1. **Object Header (Заголовок объекта)**: Занимает 12-16 байт в 64-битной JVM. Состоит из `Mark Word` (8 байт — хэшкод, статус блокировки, возраст для GC) и `Klass Word` (ссылка на метаданные класса, 4 байт с сжатыми указателями Compressed OOPs, иначе 8 байт). 2. **Примитивы**: Занимают фиксированный размер (boolean/byte — 1 байт, int/float — 4 байта, long/double — 8 байт). 3. **Padding (Выравнивание)**: Размер объекта в памяти всегда выравнивается до числа, кратного 8 байтам. * **Compressed OOPs**: Оптимизация JVM, сжимающая указатели с 8 байт до 4 байт на кучах размером до 32 ГБ, что экономит до 20-30% памяти.
Lock-Free алгоритмы позволяют нескольким потокам безопасно работать с общей памятью без использования блокировок (synchronized/Locks). Они не усыпляют потоки, предотвращая взаимные блокировки (deadlocks). * **CAS (Compare-And-Swap)**: Атомарная инструкция процессора. Она принимает три аргумента: адрес памяти (V), старое ожидаемое значение (A) и новое значение (B). Если значение по адресу V равно A, оно заменяется на B. Если нет, операция завершается ошибкой (или повторяется в цикле). * Примеры в Java: Классы атомиков (`AtomicInteger`, `AtomicReference`), основанные на методах класса `Unsafe` / `VarHandle`.
* **`java.lang.OutOfMemoryError: Java heap space`**: Закончилась память в куче для создания новых объектов. Лечится оптимизацией кода или увеличением `-Xmx`. * **`java.lang.OutOfMemoryError: Metaspace`**: Закончилась память для метаданных классов (класс-лоадеры, методы). Часто возникает при динамической генерации классов без выгрузки ClassLoader. * **`java.lang.OutOfMemoryError: GC overhead limit exceeded`**: Более 98% времени тратится на сборку мусора, а освобождается менее 2% кучи. Сигнал о критической нехватке памяти. * **Отладка**: Использовать флаги `-XX:+HeapDumpOnOutOfMemoryError` и `-XX:HeapDumpPath` для снятия дампа памяти во время падения и анализа в Eclipse Memory Analyzer (MAT) или JProfiler.
Java Agent — это программа, использующая API пакета `java.lang.instrument` для модификации скомпилированного байткода классов на лету во время загрузки JVM. * **Запуск**: Агенты подключаются через опцию `-javaagent:agent.jar` (статически) или динамически присоединяются во время работы приложения с помощью VirtualMachine Attach API. * **Инструментация**: Агент регистрирует экземпляр `ClassFileTransformer`. При загрузке класса загрузчиком классов байткод передается трансформеру в виде массива байт, модифицируется (например, добавляется логирование, профилирование с помощью библиотек ASM, ByteBuddy или Javassist) и возвращается JVM для загрузки.
* **JDK Dynamic Proxy**: Позволяет создавать прокси-объекты только для классов, которые реализуют хотя бы один интерфейс. Прокси-класс генерируется динамически в рантайме, реализует те же интерфейсы и перенаправляет вызовы через `InvocationHandler`. * **CGLIB Proxy**: Использует генерацию байткода с помощью библиотеки CGLIB для создания подкласса (subclass) целевого объекта. Позволяет проксировать обычные классы без интерфейсов. Не может проксировать `final` классы или `final`/`private` методы (поскольку их невозможно переопределить).
Spring AOP (Aspect-Oriented Programming) построен на динамических прокси-серверах (Proxy Pattern). Во время инициализации контекста Spring оборачивает бины, к которым применяются аспекты (например, `@Transactional`), в прокси. * По умолчанию, если целевой класс реализует интерфейс, используется **JDK Dynamic Proxy**. Если интерфейсов нет — используется **CGLIB**. * При вызове метода прокси-класс сначала запускает цепочку перехватчиков (Aspects/Advices: Before, Around, After), а затем передает управление оригинальному бину.
* **Кэш 1-го уровня (L1)**: Привязан к объекту `Session`. Включен всегда. Защищает от дублирования SQL-запросов к одной записи в рамках одной транзакции. Если сущность запрошена дважды в одной сессии, второй раз она вернется из памяти. * **Кэш 2-го уровня (L2)**: Привязан к `SessionFactory`. По умолчанию отключен. Разделяется между сессиями (шарится на всё приложение). Настраивается с помощью провайдеров (Ehcache, Redis, Hazelcast) для кэширования часто читаемых и редко изменяемых данных.
Пессимистическая блокировка предотвращает параллельное изменение данных на уровне СУБД с помощью SQL-конструкций: * **`PESSIMISTIC_WRITE`**: Блокирует строку базы данных для записи и чтения другими транзакциями. В SQL транслируется как `SELECT ... FOR UPDATE`. Другие транзакции будут ждать снятия блокировки. * **`PESSIMISTIC_READ`**: Захватывает разделяемую блокировку (shared lock). Позволяет другим транзакциям читать строку, но запрещает ее модифицировать. В SQL транслируется как `SELECT ... FOR SHARE` (в зависимости от СУБД).
`LazyInitializationException` возникает, когда код обращается к лениво-инициализированной (FetchType.LAZY) коллекции или связи за пределами открытой сессии Hibernate (после закрытия Session/EntityManager). **Решения**: 1. Использовать `JOIN FETCH` в запросах JPQL/Criteria API, чтобы загрузить связь сразу в рамках одной сессии. 2. Настроить `@EntityGraph` для явного указания загружаемых связей. 3. Перенести логику работы с коллекциями внутрь сервисного слоя (помеченного `@Transactional`), где сессия остается открытой.
* **`@Controller`**: Используется в классическом Spring MVC. Его методы обычно возвращают имя представления (шаблона HTML, JSP), которое обрабатывается `ViewResolver`. * **`@RestController`**: Является составной аннотацией, объединяющей `@Controller` и `@ResponseBody`. Все его методы автоматически сериализуют возвращаемый результат в тело ответа (обычно JSON или XML) с помощью `HttpMessageConverter`.
Spring Security работает на основе цепочки сервлетных фильтров (**DelegatingFilterProxy** и **FilterChainProxy**), которые перехватывают все HTTP-запросы до их попадания в контроллеры. Основные этапы аутентификации: 1. Фильтр (`UsernamePasswordAuthenticationFilter`) извлекает учетные данные из запроса. 2. Данные оборачиваются в токен (`UsernamePasswordAuthenticationToken`) и передаются в `AuthenticationManager`. 3. `AuthenticationManager` делегирует проверку одному или нескольким `AuthenticationProvider`. 4. Провайдер обращается к `UserDetailsService` для загрузки пользователя из БД. 5. При успехе созданный объект аутентификации сохраняется в `SecurityContextHolder`.
`Optional` предназначен исключительно для возвращаемых значений методов, чтобы явно показать вызывающему коду возможность отсутствия результата. **Антипаттерны**: * Использование `Optional` в качестве типов полей классов (он не реализует `Serializable`). * Передача `Optional` в качестве аргументов методов (создает лишнюю вложенность и усложняет код). * Использование `Optional` для оборачивания коллекций (для пустой коллекции правильнее возвращать пустой список `Collections.emptyList()`). * Вызов `.get()` без предварительной проверки `.isPresent()` (что может приводить к `NoSuchElementException`).
* **`map`**: Выполняет преобразование 1-в-1. Применяет функцию к каждому элементу потока и возвращает поток измененных элементов (например, преобразует `Stream<User>` в `Stream<String>` имен). * **`flatMap`**: Выполняет преобразование 1-в-многие. Применяет функцию, которая преобразует каждый элемент в другой поток, а затем «сглаживает» полученные потоки в один единый поток (например, преобразует `Stream<Department>` с коллекцией сотрудников в общий плоский `Stream<Employee>`).
Double-Checked Locking используется для ленивой инициализации потокобезопасного Singleton. До Java 5 (JMM v1.0) поле синглтона без `volatile` могло привести к возврату недоинициализированного объекта. Из-за переупорядочивания инструкций процессором (instruction reordering) запись ссылки на объект в переменную могла произойти *до* выполнения конструктора объекта. Другой поток видел переменную не-null, читал ее и работал с сырым, недоконструированным объектом. В Java 5 ввели семантику `volatile`, запрещающую переупорядочивание операций записи объекта с операцией записи в volatile-ссылку.
В Java 8+ архитектура `ConcurrentHashMap` была переписана. Отказались от сегментов (Segment lock). * Блокировка происходит на уровне **отдельного бакета (ячейки массива)** с помощью встроенного монитора `synchronized` на первом узле связанного списка или дерева. * Для вставки первого элемента в пустой бакет используется неблокирующий алгоритм **CAS** (Compare-And-Swap). * Чтение (`get`) происходит без блокировок вообще за счет использования `volatile` указателей на данные узлов, что дает высокую производительность при преобладании чтений.
`WeakHashMap` — это реализация интерфейса `Map`, в которой ключи имеют тип слабой ссылки (`WeakReference`). * **Принцип**: Если на объект-ключ больше нет сильных ссылок во всем приложении, этот ключ (и связанное значение) автоматически удаляется из `WeakHashMap` при следующей сборке мусора. * **Применение**: Организация кэшей метаданных, кэширования результатов вычислений, предотвращающих утечки памяти, когда основные объекты приложения выгружаются из памяти.
Sealed (запечатанные) классы и интерфейсы (появились в Java 17) позволяют строго ограничить список классов, которые могут наследоваться от них. * **Объявление**: Указывается ключевое слово `sealed` и список разрешенных наследников через `permits` (например, `public sealed class Shape permits Circle, Square`). * **Наследники**: Разрешенные наследники должны быть объявлены как `final` (нельзя дальше наследовать), `sealed` (продолжают контролировать наследников) или `non-sealed` (открыты для любого наследования). * Помогает компилятору выполнять безопасный паттерн-матчинг (исчерпывающий switch без `default`).