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

时间:2015-03-18 21:18:06

标签: lambda java-8 lazy-initialization

我想在没有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的情况下实现这一目标?

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

14 个答案:

答案 0 :(得分:34)

在您的实际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属性的并发调用返回相同的结果。

答案 1 :(得分:20)

考虑Miguel Gamboa’s solution并试图在不牺牲其优雅的情况下最小化每个字段的代码,我得出以下解决方案:

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 Marks’s solution略大,但它保留了原始解决方案的nice属性,在第一个查询之后,只有一个轻量级Supplier无条件地返回已经计算值。

答案 2 :(得分:19)

Miguel Gamboa's answer采用的方法很好:

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

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

答案 3 :(得分:10)

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

答案 4 :(得分:3)

支持,

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

  • @FunctionalInterface注释(允许在声明中指定lambda)
  • default关键字(定义实现,就像抽象类一样 - 但在界面中)

可以获得与在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

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

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

答案 5 :(得分:2)

这个怎么样?那么你可以使用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();
    }
}

答案 6 :(得分:1)

你可以沿着这些方向做点什么:

   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复制了代码。

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

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

答案 7 :(得分:1)

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

答案 8 :(得分:1)

如果你需要一些近似于C#中Lazy行为的东西,这会给你线程安全并保证你总是得到相同的值,那么就没有简单的方法可以避免if。 / p>

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

答案 9 :(得分:1)

好吧,我并不是真的建议没有&#34;如果&#34;,但这是我对此事的看法:

一种简单的方法是使用AtomicReference(三元运算符仍然像&#34; if&#34;):

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

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

然而,有一个人可能不需要的整个线程安全魔法。所以我像Miguel那样做了一点点扭曲:

由于我喜欢简单的单行,我只是使用三元运算符(再次,读起来像&#34; if&#34;)但是我让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上面的字段修改示例,如果没有&#34; if&#34;也可以进一步简化。我们不需要界面。我们只需要利用上面的&#34;技巧&#34;再次:赋值运算符的结果是赋值,我们可以使用括号来强制求值。所以使用上面的方法它只是:

我们所需要的只是:

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


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

答案 10 :(得分:0)

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

}

答案 11 :(得分:0)

斯图尔特马克的解决方案,有一个明确的课程。 (我认为这是否是&#34;更好&#34;是个人偏好的事情。)

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

}

答案 12 :(得分:0)

2个解决方案,一个功能,然后一个对象(相同的代码),线程安全没有“ if” ,并且使用正确的类型传播(这里没有解决办法)。

这很短。由运行时处理的更好的惰性字段支持最终将使此代码过时...

用法:

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

final Lazy<Integer, RuntimeException> finalFunc = lazyField(() -> value(1)); // less efficient than object : there is one "if" and 2 instances. "null check removal" may remove the if...

private Lazy<Integer, IOException> lazyFunc = lazyField(() -> this.lazyFunc = value(1)); // more efficient than object, 1 instance

private static final Lazy<Integer, AnyException> lazyTest = lazyField(() -> {throw new AnyException();});

首先,我定义一个带有异常的lambda:

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

然后是惰性类型:

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

功能版本:

它直接返回一个lambda,如果不像上面的示例中那样在最终字段上使用,则最终将获得较少的内存占用。

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> value(T value) {
    return () -> value;
}

一个人不能强制给出正确的值回调,至少它总是返回相同的结果,但不能避免“ 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(() -> value = value(supplier.get()));
    }

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

享受

答案 13 :(得分:-1)

这是使用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));
    }
}