Инициализация ленивого поля с помощью лямбда

Я хотел бы реализовать инициализацию ленивого поля (или отложенную инициализацию) без инструкции if и использования lambdas. Итак, я хотел бы иметь такое же поведение следующего свойства 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?

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

Ответ 1

В вашей реальной лямбда вы можете просто обновить 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 возвращают тот же результат.

Ответ 2

Принимая решение Miguel Gamboas и пытаясь свести к минимуму код для каждого поля, не жертвуя его элегантностью, я пришел к следующему решению:

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()));

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

Ответ 3

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

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

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

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

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

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-условного (на самом деле, это довольно дешево) или больше того, чтобы избежать повторения if-условного условия в каждом получателе. Я предположил, что это последнее, и мое предложение касается этой озабоченности. Если вы беспокоитесь о превышении времени выполнения ex-условного кода, тогда вам также следует взять на себя служебные обязанности при вызове лямбда-выражения.

Ответ 5

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

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

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

Можно получить то же поведение Lazy<T>, что и на С#.


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

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

Ответ 6

Как насчет этого? то вы можете сделать что-то подобное, используя 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();
    }
}

Ответ 7

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

   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();
   }

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

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

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

Ответ 8

Здесь также работает, если вы хотите передать аргументы (которые вы не имеете при инициализации функционального интерфейса) вашему методу 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);

Ответ 9

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

Вам нужно будет использовать нестабильное поле и двойную проверку блокировки. Ниже приведена самая низкая версия памяти для класса, которая дает вам поведение С#:

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();
    }
}

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

    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());

Ответ 10

Ну, на самом деле я не предлагаю не иметь "если", но вот мое мнение по этому вопросу:

Один простой метод - использовать 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, который работает без "if", также может быть дополнительно упрощен. Нам не нужен интерфейс. Нам просто нужно снова использовать вышеупомянутый "трюк": результатом оператора присваивания является присвоенное значение, мы можем использовать скобки для форсирования порядка вычисления. Так что с методом выше это просто:

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


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

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

Ответ 11

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

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();
    }

}

Ответ 12

Решение Stuart Mark с явным классом. (Я думаю, что это "лучше" - это личное предпочтение).

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());
}

}

Ответ 13

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 and one "if". null check removal may remove the "if"...
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 () -> {
        if(field[0] == null) {
            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(() -> value = eval(supplier.get()));
    }

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

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

Ответ 14

Вот решение, использующее Java Proxy (отражение) и поставщик 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));
    }
}