Ответ
Ленивая инициализация — это паттерн отложенной инициализации, при котором ресурсоёмкий объект создаётся не в момент загрузки класса или создания владеющего объекта, а в момент первого обращения к нему. Цель — оптимизация использования памяти и ускорение старта приложения.
Типичные сценарии использования:
- Создание «тяжёлых» зависимостей (соединения с БД, клиенты внешних сервисов).
- Реализация паттерна Singleton.
- Настройка отношений
@ManyToOne/@OneToOneв JPA (fetch = FetchType.LAZY).
Примеры реализации в Java:
1. Простая (не потокобезопасная) версия:
public class LazyResourceHolder {
private ExpensiveResource resource = null;
public ExpensiveResource getResource() {
if (resource == null) {
resource = new ExpensiveResource(); // Создание при первом вызове
}
return resource;
}
}
2. Потокобезопасная версия с «удерживателем» (Initialization-on-demand holder idiom): Наиболее эффективный и безопасный способ для Singleton.
public class Singleton {
// Приватный конструктор
private Singleton() {}
// Статический внутренний класс содержит экземпляр
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // Загрузка класса Holder и инициализация INSTANCE
// происходит только здесь (лениво и потокобезопасно)
}
}
3. С использованием Supplier (Java 8+):
public class Lazy<T> {
private Supplier<T> supplier;
private T value;
public Lazy(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (value == null) {
value = supplier.get();
}
return value;
}
}
// Использование
Lazy<ExpensiveResource> lazyResource = new Lazy<>(() -> new ExpensiveResource());
// ...
ExpensiveResource res = lazyResource.get(); // Создаётся здесь
Плюсы: Экономия памяти, ускорение инициализации. Минусы: Усложнение кода, необходимость контроля потокобезопасности, риск ошибок NullPointerException.
Ответ 18+ 🔞
А, ленивая инициализация! Ну это ж классика, блядь, как Герасим с Муму. Всё откладываем на потом, пока жопа не загорится, сука.
Смотри, в чём прикол. Представь, у тебя есть объект, который создаётся, как ёперный театр — долго и ресурсов жрёт, как не в себя. Соединение с базой, какой-нибудь тяжёлый клиент API. И зачем его создавать сразу, когда приложение стартует, если он, возможно, вообще не понадобится? Вот тут и приходит наш герой — ленивый чувак, который говорит: «Да похуй, создам, когда спросят».
Как это выглядит в коде, если делать по-простому, на коленке:
public class LazyResourceHolder {
private ExpensiveResource resource = null; // Пока пусто, блядь, как в моей голове по утрам.
public ExpensiveResource getResource() {
if (resource == null) { // А если никто не спрашивал?
resource = new ExpensiveResource(); // Вот щас, блядь, на тебе! Создаём в первый и последний раз.
}
return resource;
}
}
Но стоп, ёпта! А если с двух потоков одновременно придут и спросят? Оба увидят null, оба начнут создавать этот тяжёлый объект, и получится у нас две штуки, а надо одну. Пиздец, а не оптимизация. Нас ждёт race condition, волнение ебать, терпения ноль.
Поэтому умные дядьки придумали хитрую жопу — Initialization-on-demand holder idiom. Звучит, как диагноз, но работает, блядь, на ура. Особенно для синглтонов.
public class Singleton {
private Singleton() {} // Конструктор спрятали, чтобы не выёбывались.
// А вот тут магия, в рот меня чих-пых!
private static class Holder {
static final Singleton INSTANCE = new Singleton(); // Создаётся внутри статического класса.
}
public static Singleton getInstance() {
return Holder.INSTANCE; // В этот момент JVM загружает класс Holder и инициализирует INSTANCE.
// И делает она это лениво, безопасно и один раз, как мой дед пасху святил.
}
}
Красота, блядь! Потокобезопасно за счёт ClassLoader, и лениво. Пока не вызовешь getInstance() — нихуя не происходит.
Ну а если насовременней, с Java 8, то можно через Supplier:
public class Lazy<T> {
private Supplier<T> supplier; // Поставщик нашего добра.
private T value; // А вот где оно будет храниться.
public Lazy(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (value == null) {
value = supplier.get(); // Тыкаем в поставщика, он выдаёт товар.
}
return value;
}
}
// Использование
Lazy<ExpensiveResource> lazyResource = new Lazy<>(() -> new ExpensiveResource());
// ...
ExpensiveResource res = lazyResource.get(); // Вот тут-то всё и рождается, ебанашка!
Итоги, блядь:
- Плюсы: Памяти меньше жрём, стартуем быстрее. Не создаём то, что не нужно. Ядрёна вошь!
- Минусы: Код чуть сложнее. Надо думать про потоки, а то будет, как у Герасима — хотел как лучше, а получил... ну ты понял. И если напортачить, можно нарваться на
NullPointerExceptionв самый неподходящий момент.
Короче, инструмент охуенный, но без фанатизма. Как и всё в этой жизни, блядь.