使用 lambda 进行惰性字段初始化

Lazy field initialization with lambdas

我想在没有 if 语句的情况下并利用 lambda 来实现惰性字段初始化(或延迟初始化)。因此,我希望具有与以下 Foo 属性 相同的行为,但没有 if:

class A<T>{
    private T fooField;

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

忽略此解决方案不能保证安全使用的事实:1) 多线程; 2) null作为T.

的有效值

因此,为了表达将 fooField 的初始化推迟到第一次使用时的意图,我想声明类型 Supplier<T>fooField,例如:

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

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

然后在 getFoo 属性 中我会 return fooField.get()。但是现在我希望对 getFoo 属性 的下一次调用避免 expensiveInit() 而只是 return 前一个 T 实例。

如何在不使用 if 的情况下实现这一点?

尽管有命名约定并将 -> 替换为 =>,但也可以在 C# 中考虑此示例。但是,NET Framework 版本 4 已经提供了具有所需语义的 Lazy<T>

您可以按照以下方式做一些事情:

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

我最近看到这是 Venkat Subramaniam 的想法。我从 this page.

复制了代码

基本思想是供应商一旦被调用,就会用一个更简单的工厂实现替换自己,returns 初始化实例。

这是在线程安全的单例惰性初始化上下文中,但显然您也可以将其应用于普通字段。

在您实际的 lambda 中,您可以简单地用新的 lambda 更新 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 属性 return 的并发调用相同结果。

采取的方法很好:

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

它适用于一次性惰性字段。但是,如果需要以这种方式初始化多个字段,则必须复制和修改样板文件。另一个字段必须像这样初始化:

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

如果您可以在初始化后每次访问一次额外调用一个方法,我会按如下方式进行。首先,我将编写一个高阶函数,returns 包含缓存值的 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;
        }
    };
}

此处调用匿名class,因为它具有可变状态,即初始化值的缓存。

然后,创建许多延迟初始化的字段变得非常容易:

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

注意:我在问题中看到它规定了"without using an if"。我不清楚这里的问题是避免 if 条件的运行时昂贵(实际上,它非常便宜)还是更多地是避免在每个 getter 中重复 if 条件。我认为是后者,我的提案解决了这个问题。如果您担心 if 条件的运行时开销,那么您还应该考虑调用 lambda 表达式的开销。

如果您想将参数(在初始化功能接口时没有参数)传递给您的 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));
    }
}

用法同答案:

private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);

采用 并尝试在不牺牲其优雅的情况下最小化每个字段的代码,我得出以下解决方案:

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 无条件 returns 已经计算的值。

Project Lombok provides a @Getter(lazy = true) 完全满足您需要的注释。

支持,

通过创建一个小界面并结合 java 8 中引入的 2 个新功能:

  • @FunctionalInterface 注释(允许在声明时分配一个 lambda)
  • default 关键字(定义一个实现,就像抽象 class - 但在一个接口中)

可以获得与您在 C# 中看到的相同的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

以下代码片段演示了此助手的生命周期 class

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

这个怎么样?然后你可以使用 Apache Commons 中的 LazyInitializer 来做这样的事情: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();
    }
}

这个怎么样。 一些 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();
    }

}

这是一个使用 Java 的代理(反射)和 Java 8 供应商的解决方案。

* 因为使用Proxy,发起的对象必须实现传递的接口。

* 与其他解决方案的不同之处在于从使用开始的封装。您开始直接使用 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));
    }
}

如果您需要类似于 C# 中 Lazy 行为的东西,它可以为您提供线程安全并保证您始终获得相同的值,则没有直接的方法来避免 if .

您将需要使用可变字段和双重检查锁定。这是 class 的最低内存占用版本,它为您提供 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());

Stuart Mark 的解决方案,带有明确的 class。 (我认为是否"better"是个人喜好问题。)

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

}

嗯,我真的不建议没有 "if",但这是我对此事的看法:

一个简单的方法是使用 AtomicReference(三元运算符仍然像 "if"):

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

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

但是还有一个人可能不需要的整个线程安全魔法。所以我会像 Miguel 那样稍微改变一下:

因为我喜欢简单的单行代码,所以我只使用了一个三元运算符(同样,读起来像 "if"),但我会让 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" 的情况下也可以工作,也可以进一步简化。我们不需要接口。我们只需要再次利用上面的 "trick" :赋值运算符的结果是赋值,我们可以使用括号来强制计算顺序。所以用上面的方法就是:

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


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

请注意,您不能在不失去惰性的情况下内联 "value(...)" 方法。

2 种解决方案,一种函数式 then 和一种对象(代码相同),线程安全无"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();});

首先我定义了一个 lambda 异常:

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

然后是 Lazy 类型:

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

功能版:

它直接 returns 一个 lambda,如果没有像上面的示例那样在 final 字段上使用,最终会占用更少的内存。

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

不能强制给出正确的值回调,至少它总是returns相同的结果但可能无法避免"if"(如在最终字段情况下)。

对象版本:

从外面完全安全。

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字段保证了写入该字段的值的顺序。如果在有效设置惰性值之后调用,则在此字段中设置的初始 lambda 也将 returns 初始化惰性值。

享受