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

rod*_*ino 46 lambda lazy-initialization java-8

我想在没有if语句的情况下实现惰性字段初始化(或延迟初始化)并利用lambdas.所以,我想具有以下Foo属性的相同行为,但没有if:

class A<T>{
    private T fooField;

    public T getFoo(){
        if( fooField == null ) fooField = expensiveInit();
        return fooField;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

   public T getFoo(){
      return fooField.get();
   }
}
Run Code Online (Sandbox Code Playgroud)

然后在getFoo财产中我会回来fooField.get().但是现在我想要对getFoo属性的下一次调用避免expensiveInit()并且只返回前一个T实例.

如何在不使用的情况下实现这一目标if

尽管命名约定并替换了->by =>,但是这个例子也可以在C#中考虑.但是,.NET Framework版本4已经提供了Lazy<T>所需的语义.

Mig*_*boa 36

在您的实际lambda中,您只需fooField使用新的lambda 更新,例如:

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

    public T getFoo(){
       return fooField.get();
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,此解决方案不像.Net那样是线程安全的Lazy<T>,并且不能确保对getFoo属性的并发调用返回相同的结果.


Hol*_*ger 22

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

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()));
Run Code Online (Sandbox Code Playgroud)

每个字段的代码只比Stuart Marks的解决方案略大,但它保留了原始解决方案的优良特性,在第一个查询之后,只有一个轻量级Supplier无条件地返回已经计算的值.

  • 你的懒惰功能很棒.我不得不带我去处理它! (2认同)

Stu*_*rks 20

采取的办法米格尔·甘博亚的答案是一个很好的一个:

private Supplier<T> fooField = () -> {
   T val = expensiveInit();
   fooField = () -> val;
   return val;
};
Run Code Online (Sandbox Code Playgroud)

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

private Supplier<T> barField = () -> {
   T val = expensiveInitBar();          // << changed
   barField = () -> val;                // << changed
   return val;
};
Run Code Online (Sandbox Code Playgroud)

如果在初始化后每次访问可以支持一个额外的方法调用,我将按如下方式执行.首先,我编写一个高阶函数,它返回包含缓存值的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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

这里调用一个匿名类,因为它具有可变状态,即初始化值的缓存.

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

Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());
Run Code Online (Sandbox Code Playgroud)

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

  • @MiguelGamboa看起来我们都在建立彼此的想法! (2认同)

Mar*_*tek 10

Project Lombok提供了一个@Getter(lazy = true)注释,可以完全满足您的需求.


小智 5

这个怎么样?那么你可以通过使用LazyInitializerApache 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @HiddenDragon 您可能应该在答案中提及 apache commons。 (4认同)