Инициализация ленивого поля с помощью лямбда
Я хотел бы реализовать инициализацию ленивого поля (или отложенную инициализацию) без инструкции 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-условного кода, тогда вам также следует взять на себя служебные обязанности при вызове лямбда-выражения.
Ответ 4
Проект Lombok предоставляет аннотацию @Getter(lazy = true)
, которая делает именно то, что вам нужно.
Ответ 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();
}
Следующий фрагмент демонстрирует жизненный цикл этого вспомогательного класса
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
Ответ 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));
}
}