Ленивая инициализация поля с лямбда-выражениями

Я хотел бы реализовать ленивую инициализацию поля (или отложенную инициализацию) без оператора if и с использованием лямбда-выражений. Итак, я хотел бы иметь такое же поведение следующего свойства Foo, но без if:

class A<T>{
    private T fooField;

    public T getFoo(){
        if( fooField == null ) fooField = expensiveInit();
        return fooField;
    }
}

Не обращайте внимания на тот факт, что это решение не гарантирует безопасного использования для: 1) многопоточности; 2) null как допустимое значение T.

Итак, чтобы выразить намерение отложить инициализацию fooField до его первого использования, я хотел бы объявить fooField типа Supplier<T>, например:

class A<T>{
   private Supplier<T> fooField = () -> expensiveInit();

   public T getFoo(){
      return fooField.get();
   }
}

а затем в свойстве getFoo я бы просто вернул fooField.get(). Но теперь я хочу, чтобы следующие вызовы свойства getFoo избегали expensiveInit() и просто возвращали предыдущий экземпляр T.

Как я могу добиться этого, не используя if?

Несмотря на соглашения об именах и замену ->на =>, этот пример можно было бы рассматривать и на C#. Однако NET Framework версии 4 уже предоставляет Lazy<T> с нужной семантикой.


person rodolfino    schedule 18.03.2015    source источник
comment
Просто поставьте лайк этому сообщению stackoverflow.com/questions/6847721/ когда-должен-мне-лениться   -  person Fals    schedule 19.03.2015
comment
Это для .Net. Но есть ли эквивалент Lazy в Java?   -  person rodolfino    schedule 19.03.2015
comment
@rodolfino Я думаю, что некоторая путаница заключается в том, что в вашем вопросе есть теги C # и Java-8. Возможно, вы могли бы отредактировать вопрос, чтобы было более ясно, что вам нужно решение Java-8, которое имитирует поведение C #.   -  person ryanyuyu    schedule 19.03.2015
comment
См. Suppliers.memoize   -  person Ben Manes    schedule 20.03.2015
comment
Связано: stackoverflow.com/questions /8297705/   -  person AlikElzin-kilaka    schedule 11.01.2017
comment
Вы также можете ознакомиться с решением vavr.   -  person gce    schedule 13.07.2017


Ответы (14)


В вашей фактической лямбде вы можете просто обновить fooField новой лямбдой, например:

class A<T>{
    private Supplier<T> fooField = () -> {
       T val = expensiveInit();
       fooField = () -> val;
       return val;
    };

    public T getFoo(){
       return fooField.get();
    }
}

Опять же, это решение не является потокобезопасным, как .Net Lazy<T>, и не гарантирует, что одновременные вызовы свойства getFoo вернут тот же результат.

person Miguel Gamboa    schedule 18.03.2015

Подход, использованный ответом Мигеля Гамбоа, прекрасен:

private Supplier<T> fooField = () -> {
   T val = expensiveInit();
   fooField = () -> val;
   return val;
};

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

private Supplier<T> barField = () -> {
   T val = expensiveInitBar();          // << changed
   barField = () -> val;                // << changed
   return val;
};

Если вы можете выдержать один дополнительный вызов метода для доступа после инициализации, я бы сделал это следующим образом. Во-первых, я бы написал функцию более высокого порядка, которая возвращает экземпляр Supplier, содержащий кэшированное значение:

static <Z> Supplier<Z> lazily(Supplier<Z> supplier) {
    return new Supplier<Z>() {
        Z value; // = null
        @Override public Z get() {
            if (value == null)
                value = supplier.get();
            return value;
        }
    };
}

Здесь вызывается анонимный класс, потому что он имеет изменяемое состояние, которое является кэшированным инициализированным значением.

Затем становится довольно легко создать множество полей с ленивой инициализацией:

Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());

Примечание. Я вижу в вопросе, что в нем указано «без использования if». Мне было неясно, связана ли здесь проблема с тем, чтобы избежать затрат времени выполнения на if-conditional (на самом деле, это довольно дешево), или же это больше связано с тем, чтобы избежать повторения if-conditional в каждом геттере. Я предположил, что это последнее, и мое предложение направлено на решение этой проблемы. Если вас беспокоят накладные расходы во время выполнения if-conditional, вам также следует принять во внимание накладные расходы на вызов лямбда-выражения.

person Stuart Marks    schedule 19.03.2015
comment
Каковы будут недостатки ленивой реализации с захваченным AtomicReference‹Supplier‹Z››? - person srborlongan; 19.03.2015
comment
Пример реализации: static ‹Z› Поставщик‹Z› лениво(Поставщик‹Z› поставщик) { AtomicReference‹Supplier‹Z›› value = new AtomicReference‹›(); return() -> value.updateAndGet(v -> v -= null ? supplier.get() : v); } - person srborlongan; 19.03.2015
comment
@srborlongan Вы имели в виду AtomicReference‹Z›? - person Stuart Marks; 19.03.2015
comment
@srborlongan Это сработает, но, к сожалению, updateAndGet() на самом деле не имеет нужной нам семантики транзакций. Его можно вызывать несколько раз, поэтому документ предостерегает от побочных эффектов. В случае с ОП этого не произойдет, но все же. Кроме того, безусловный updateAndGet() каждый раз вызывает непостоянное чтение и запись. Может быть, это не имеет большого значения, но строго не обязательно. - person Stuart Marks; 19.03.2015
comment
@srborlongan Вы также можете использовать AtomicReference‹Supplier‹Z››, инициализировать его с помощью тяжеловесного инициализатора, который устанавливает поставщика, который возвращает кеш в следующий раз. На данный момент AtomicRef является просто держателем ссылки; с таким же успехом можно просто написать класс и использовать поле. - person Stuart Marks; 19.03.2015
comment
@StuartMarks, хорошее применение моего подхода. Я оценил идею полезной функции lazily - person Miguel Gamboa; 19.03.2015
comment
@MiguelGamboa Похоже, мы все опираемся на идеи друг друга! - person Stuart Marks; 19.03.2015
comment
@MiguelGamboa и Стюарт - FWIW Я добавил этот и многие другие методы здесь, чтобы этот тест. JIT хорошо справляется со всеми из них, а стоимость в значительной степени незначительна по сравнению с неленивым подходом, поэтому я бы не беспокоился о производительности и просто выбрал то, что является самым чистым для каждого поля и наиболее функциональным. - person BeeOnRope; 17.12.2016

Взяв решение Мигеля Гамбоа и попытавшись минимизировать код для каждого поля, не жертвуя его элегантностью, я пришел к следующему решению:

interface Lazy<T> extends Supplier<T> {
    Supplier<T> init();
    public default T get() { return init().get(); }
}
static <U> Supplier<U> lazily(Lazy<U> lazy) { return lazy; }
static <T> Supplier<T> value(T value) { return ()->value; }

Supplier<Baz> fieldBaz = lazily(() -> fieldBaz=value(expensiveInitBaz()));
Supplier<Goo> fieldGoo = lazily(() -> fieldGoo=value(expensiveInitGoo()));
Supplier<Eep> fieldEep = lazily(() -> fieldEep=value(expensiveInitEep()));

Код для каждого поля лишь немного больше, чем в решении Стюарта Маркса, но он сохраняет хорошее свойство исходного решения, которое после первом запросе будет только облегченный Supplier, который безоговорочно возвращает уже вычисленное значение.

person Holger    schedule 19.03.2015
comment
Ваша ленивая функция потрясающая. Я должен был взять I в то время как обработать его! - person Miguel Gamboa; 24.03.2015
comment
Одним из недостатков этого подхода является то, что fieldBaz изменяет реализации до/после первой инициализации. Поскольку профили точек доступа вызывают сайты, чтобы увидеть, сколько различных реализаций находится на сайте вызова, я думаю, что это приводит к несколько менее оптимистичному коду на сайте вызова. По сути, стоимость получения приятного безусловного поставщика, вероятно, представляет собой немного более сложный код на сайте вызова (хотя в конечном итоге он может иметь ровно 1 скрытую ветвь, которая, вероятно, такая же, как решение Стюарта). - person BeeOnRope; 16.12.2016
comment
@BeeOnRope: я сомневаюсь, что поведение первого вызова имеет какое-либо значение, особенно в текущей JVM, использующей многоуровневую компиляцию. Наличие «почти мономорфных» сайтов вызовов, которые редко меняются, очень распространено и должно учитываться разумным оптимизатором. Когда изменение происходит только при первом вызове, даже не нужно ничего де-оптимизировать (если только expensiveInit…() не окажется на самом деле тривиальным методом). - person Holger; 16.12.2016
comment
@Holger: действительно, компиляторы хорошо компилируют сайты с разной степенью полиморфизма, но они все равно создают другой код, если на сайте вызова когда-либо был замечен только один класс, а не 2, 3 или больше и т.д. (и это зависит от частот). Возможно, вы правы в том, что, поскольку другой тип используется только при первом вызове, возможно, JIT даже не узнает, что один раз использовался другой, потому что в этот момент статистика не велась. Также может случиться так, что биморфный код так же быстр, как и консервативный мономорфный код (но немного больше). - person BeeOnRope; 16.12.2016
comment
FWIW, я сравнил почти все методы здесь с JMH. Все они очень близки, в основном примерно на 1 цикл (~ 0,3 нс) медленнее, чем неленивый метод по умолчанию для возврата объекта static final. Кажется, что примерно половина стоимости связана с дженериками и стиранием — T стирается до Object, которое необходимо привести обратно к Integer, когда возвращаются методы теста (приведение также необходимо в реальном мире). Если вы объявите класс Lazy для конкретного типа, вы уменьшите эту стоимость. В целом, хотя я бы просто сделал то, что проще всего, вероятно, в соответствии с предложением Стюарта. - person BeeOnRope; 17.12.2016
comment
Вот контрольный показатель на случай, если кто-то еще захочет поиграть с ним. - person BeeOnRope; 17.12.2016

Project Lombok предоставляет @Getter(lazy = true), которая делает именно то, что вам нужно.

person Marcin Kłopotek    schedule 03.02.2016

Как насчет этого? то вы можете сделать что-то подобное, используя LazyInitializer из Apache Commons: https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/concurrent/LazyInitializer.html

private static Lazy<Double> _lazyDouble = new Lazy<>(()->1.0);

class Lazy<T> extends LazyInitializer<T> {
    private Supplier<T> builder;

    public Lazy(Supplier<T> builder) {
        if (builder == null) throw new IllegalArgumentException();
        this.builder = builder;
    }
    @Override
    protected T initialize() throws ConcurrentException {
        return builder.get();
    }
}
person Hidden Dragon    schedule 25.05.2016
comment
Это не Ленивый. Это всегда будет призывать внутреннего строителя. - person rodolfino; 27.05.2016
comment
Я проверил, и это работает. Обратите внимание, что он наследуется от LazyInitializer класс. - person Hidden Dragon; 27.05.2016
comment
Это С# или Java? Я думаю, что LazyInitializer является частью стандартных библиотек .Net, но здесь вы приводите пример Java, поскольку используете аннотацию Override. - person rodolfino; 30.05.2016
comment
@rodolfino Я только что перешел на Java из .Net, но это из библиотеки Java Apache: commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/ - person Hidden Dragon; 30.05.2016
comment
@HiddenDragon Возможно, вам следует упомянуть в ответе об Apache Commons. - person Magnilex; 01.03.2017

Поддерживается,

Создав небольшой интерфейс и объединив 2 новые функции, представленные в java 8:

  • @FunctionalInterface аннотация (позволяет назначать лямбду при объявлении)
  • ключевое слово default (определяет реализацию, как абстрактный класс, но в интерфейсе)

Можно добиться того же Lazy<T> поведения, что и в C#.


использование

Lazy<String> name = () -> "Java 8";
System.out.println(name.get());

Lazy.java (скопируйте и вставьте этот интерфейс в доступное место)

import java.util.function.Supplier;

@FunctionalInterface
public interface Lazy<T> extends Supplier<T> {
    abstract class Cache {
        private volatile static Map<Integer, Object> instances = new HashMap<>();

        private static synchronized Object getInstance(int instanceId, Supplier<Object> create) {

            Object instance = instances.get(instanceId);
            if (instance == null) {
                synchronized (Cache.class) {
                    instance = instances.get(instanceId);
                    if (instance == null) {
                        instance = create.get();
                        instances.put(instanceId, instance);
                    }
                }
            }
            return instance;
        }
    }

    @Override
    default T get() {
        return (T) Cache.getInstance(this.hashCode(), () -> init());
    }

    T init();
}

Онлайн-пример — https://ideone.com/3b9alx

Следующий фрагмент демонстрирует жизненный цикл этого вспомогательного класса.

static Lazy<String> name1 = () -> { 
    System.out.println("lazy init 1"); 
    return "name 1";
};
    
static Lazy<String> name2 = () -> { 
    System.out.println("lazy init 2"); 
    return "name 2";
};

public static void main (String[] args) throws java.lang.Exception
{
    System.out.println("start"); 
    System.out.println(name1.get());
    System.out.println(name1.get());
    System.out.println(name2.get());
    System.out.println(name2.get());
    System.out.println("end"); 
}

будет выводить

start
lazy init 1
name 1
name 1
lazy init 2
name 2
name 2
end

Посмотрите онлайн-демонстрацию — https://ideone.com/3b9alx.

person Jossef Harush    schedule 29.03.2016
comment
Небезопасно использовать hashCode() в качестве ключа карты таким образом. Два объекта могут получить один и тот же хэш-код. - person Christoffer Hammarström; 14.03.2017
comment
@ChristofferHammarström, вы правы, спасибо за комментарий. поможет ли java.lang.System.identityHashCode(obj);? - person Jossef Harush; 15.03.2017
comment
Просто используйте сам объект в качестве ключа в файле IdentityHashMap. - person Christoffer Hammarström; 15.03.2017
comment
Однако есть еще одна проблема. Инвалидация кеша. Как что-то удаляется с карты? Похоже на серьезную утечку памяти. - person Christoffer Hammarström; 15.03.2017
comment
@ChristofferHammarström Спасибо! Я переключусь на саму ссылку на объект. относительно кеша: намеренно сохраняет сохраненные ссылки на результаты до тех пор, пока вы не остановите свой процесс. Я согласен, однако это следует учитывать для каждого варианта использования. - person Jossef Harush; 15.03.2017

Вы можете сделать что-то в этом роде:

   private Supplier heavy = () -> createAndCacheHeavy();

   public Heavy getHeavy()
   {
      return heavy.get();
   }

   private synchronized Heavy createAndCacheHeavy()
   {
      class HeavyFactory implements Supplier
      {
         private final Heavy heavyInstance = new Heavy();

         public Heavy get()
         {
            return heavyInstance;
         }
      }

      if(!HeavyFactory.class.isInstance(heavy))
      {
         heavy = new HeavyFactory();
      }

      return heavy.get();
   }

Недавно я увидел в этом идею Венката Субраманиама. Я скопировал код с этой страницы.

Основная идея заключается в том, что поставщик после вызова заменяет себя более простой фабричной реализацией, которая возвращает инициализированный экземпляр.

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

person bowmore    schedule 18.03.2015

Вот способ, который также работает, если вы хотите передать аргументы (которых у вас нет при инициализации функционального интерфейса) в ваш метод expensiveInit.

public final class Cache<T> {
    private Function<Supplier<? extends T>, T> supplier;

    private Cache(){
        supplier = s -> {
            T value = s.get();
            supplier = n -> value;
            return value;
        };
    }   
    public static <T> Supplier<T> of(Supplier<? extends T> creater){
        Cache<T> c = new Cache<>();
        return () -> c.supplier.apply(creater);
    }
    public static <T, U> Function<U, T> of(Function<? super U, ? extends T> creater){
        Cache<T> c = new Cache<>();
        return u -> c.supplier.apply(() -> creater.apply(u));
    }
    public static <T, U, V> BiFunction<U, V, T> of(BiFunction<? super U, ? super V, ? extends T> creater){
        Cache<T> c = new Cache<>();
        return (u, v) -> c.supplier.apply(() -> creater.apply(u, v));
    }
}

Использование совпадает с ответом Stuart Marks':

private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
person Alex - GlassEditor.com    schedule 19.03.2015

Если вам нужно что-то, что приближается к поведению Lazy в C#, что обеспечивает потокобезопасность и гарантию того, что вы всегда получаете одно и то же значение, нет простого способа избежать if.

Вам нужно будет использовать изменчивое поле и блокировку с двойной проверкой. Вот версия класса с наименьшим объемом памяти, которая обеспечивает поведение C#:

public abstract class Lazy<T> implements Supplier<T> {
    private enum Empty {Uninitialized}

    private volatile Object value = Empty.Uninitialized;

    protected abstract T init();

    @Override
    public T get() {
        if (value == Empty.Uninitialized) {
            synchronized (this) {
                if (value == Empty.Uninitialized) {
                    value = init();
                }
            }
        }
        return (T) value;
    }

}

Это не так элегантно в использовании. Вам нужно будет создать ленивые значения, подобные этому:

final Supplier<Baz> someBaz = new Lazy<Baz>() {
    protected Baz init(){
        return expensiveInit();
    }
}

Вы можете получить некоторую элегантность за счет дополнительного объема памяти, добавив фабричный метод, подобный этому:

    public static <V> Lazy<V> lazy(Supplier<V> supplier) {
        return new Lazy<V>() {
            @Override
            protected V init() {
                return supplier.get();
            }
        };
    }

Теперь вы можете создавать потокобезопасные ленивые значения просто так:

final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
final Supplier<Bar> lazyBar = lazy(() -> barInit());
final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
person Phil S    schedule 20.02.2018

Ну, на самом деле я не предлагаю отказаться от «если», но вот мой взгляд на этот вопрос:

Одним из простых способов является использование AtomicReference (тернарный оператор по-прежнему похож на «если»):

private final AtomicReference<Something> lazyVal = new AtomicReference<>();

void foo(){
    final Something value = lazyVal.updateAndGet(x -> x != null ? x : expensiveCreate());
    //...
}

Но тогда есть целая магия безопасности потоков, которая может и не понадобиться. Так что я бы сделал это, как Мигель, с небольшой изюминкой:

Поскольку мне нравятся простые однострочники, я просто использую тернарный оператор (опять же, читается как «если»), но я бы позволил порядку вычисления Java сделать свое волшебство, чтобы установить поле:

public static <T> Supplier<T> lazily(final Supplier<T> supplier) {
    return new Supplier<T>() {
        private T value;

        @Override
        public T get() {
            return value != null ? value : (value = supplier.get());
        }
    };
}

приведенный выше пример модификации поля gerardw, который работает без «если», также может быть упрощен. Нам не нужен интерфейс. Нам просто нужно снова использовать описанный выше «трюк»: результатом оператора присваивания является присвоенное значение, мы можем использовать скобки для принудительного порядка оценки. Итак, с помощью метода выше это просто:

static <T> Supplier<T> value(final T value) {
   return () -> value;
}


Supplier<Point> p2 = () -> (p2 = value(new Point())).get();

Обратите внимание, что вы не можете встроить метод "value(...)" без потери лени.

person Brixomatic    schedule 20.05.2018

Как насчет этого. Некоторые функциональные переключатели J8 позволяют избежать ошибок при каждом доступе. Предупреждение: не поддерживает потоки.

import java.util.function.Supplier;

public class Lazy<T> {
    private T obj;
    private Supplier<T> creator;
    private Supplier<T> fieldAccessor = () -> obj;
    private Supplier<T> initialGetter = () -> {
        obj = creator.get();
        creator = null;
        initialGetter = null;
        getter = fieldAccessor;
        return obj;
    };
    private Supplier<T> getter = initialGetter;

    public Lazy(Supplier<T> creator) {
        this.creator = creator;
    }

    public T get() {
        return getter.get();
    }

}
person JasonW    schedule 21.12.2016

Решение Стюарта Марка с явным классом. (Я думаю, что «лучше» это вопрос личных предпочтений.)

public class ScriptTrial {

static class LazyGet<T>  implements Supplier<T> {
    private T value;
    private Supplier<T> supplier;
    public LazyGet(Supplier<T> supplier) {
        value = null;
        this.supplier = supplier;
    }

    @Override
    public T get() {
        if (value == null)
            value = supplier.get();
        return value;
    }

}

Supplier<Integer> lucky = new LazyGet<>(()->seven());

int seven( ) {
    return 7;
}

@Test
public void printSeven( ) {
    System.out.println(lucky.get());
    System.out.println(lucky.get());
}

}

person gerardw    schedule 31.03.2018

2 решения, одно функциональное и одно объектное (это тот же код), потокобезопасный, без "if" и заботящийся об обработке исключений с правильным распространением типа (здесь нет решения, позаботьтесь об этом).

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

использование :

// object version : 2 instances (object and lambda)
final Lazy<Integer, RuntimeException> lazyObject = new LazyField<>(() -> 1);

// functional version : more efficient than object, 1 instance
// usage : wrap computed value using eval(arg), and set the lazy field with result
Lazy<Service, IOException> lazyFunc = lazyField(() -> this.lazyFunc = eval(new Service()));

// functional final version, as field is final this is less efficient than object :
// 2 instances one "if" and one sync (that could still be avoided...)
final Lazy<Integer, RuntimeException> finalFunc = lazyField(() -> eval(1));

// Here the checked exception type thrown in lambda can only be ServiceException
static Lazy<Integer, ServiceException> lazyTest = lazyField(() -> {throw new ServiceException();});

Сначала я определяю лямбду с исключением:

@FunctionalInterface
interface SupplierWithException<T, E extends Exception> {
    T get() throws E;
}

Затем ленивый тип:

interface Lazy<T, E extends Exception> extends SupplierWithException<T, E> {}

Функциональная версия:

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

static <T, E extends Exception> Lazy<T, E> lazyField(Lazy<Lazy<T, E>, E> value) {
    Objects.requireNonNull(value);
    Lazy<T, E>[] field = new Lazy[1];
    return () -> {
        synchronized(field) {
            if(field[0] == null)
                field[0] = value.get();
            return field[0].get();
        }
    };
}

static <T, E extends Exception> Lazy<T, E> eval(T value) {
    return () -> value;
}

Нельзя принудительно указать правильный обратный вызов значения, по крайней мере, он всегда возвращает один и тот же результат, но не может избежать «если» (как в случае с окончательным полем).

Версия объекта:

Полностью безопасен снаружи.

public final class LazyField<T, E extends Exception> implements Lazy<T, E> {

    private Lazy<T, E> value;

    public LazyField(SupplierWithException<T, E> supplier) {
        value = lazyField(() -> new Lazy<T, E>() {
            volatile Lazy<T, E> memBarrier;
            @Override
            public T get() throws E {
               value = memBarrier = eval(supplier.get());
            }
        });
    }

    @Override
    public T get() throws E {
        return value.get();
    }
}

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

наслаждаться

person Charles Briquel    schedule 10.07.2019

Вот решение с использованием прокси-сервера Java (отражение) и поставщика Java 8.

* Из-за использования прокси инициированный объект должен реализовывать переданный интерфейс.

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

Использование:

DataSource ds = LazyLoadDecorator.create(() -> initSomeDS(), DataSource.class)

За кулисами:

public class LazyLoadDecorator<T> implements InvocationHandler {

    private final Object syncLock = new Object();
    protected volatile T inner;
    private Supplier<T> supplier;

    private LazyLoadDecorator(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (inner == null) {
            synchronized (syncLock) {
                if (inner == null) {
                    inner = load();
                }
            }
        }
        return method.invoke(inner, args);
    }

    protected T load() {
        return supplier.get();
    }

    @SuppressWarnings("unchecked")
    public static <T> T create(Supplier<T> supplier, Class<T> clazz) {
        return (T) Proxy.newProxyInstance(LazyLoadDecorator.class.getClassLoader(),
                new Class[] {clazz},
                new LazyLoadDecorator<>(supplier));
    }
}
person AlikElzin-kilaka    schedule 11.01.2017