使用Lambda表达式进行惰性字段初始化

57
我希望能够利用lambda表达式实现延迟初始化(或者叫做延迟求值),避免使用if语句。因此,我希望拥有与下面代码中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>


只需像这篇文章一样操作即可:https://dev59.com/OGw15IYBdhLWcg3wFHpZ - Fals
这是针对 .Net 的。但是在 Java 中是否有任何等效的 Lazy 呢? - rodolfino
2
@rodolfino 我认为有些混淆的地方在于你的问题上既有C#标签又有Java-8标签。也许你可以编辑问题,使其更清晰,表明你想要一个模仿C#行为的Java-8解决方案。 - ryanyuyu
14
请参考Guava的Suppliers.memoize - Ben Manes
相关:https://dev59.com/-2sy5IYBdhLWcg3w0RTT - AlikElzin-kilaka
你也可以查看vavr解决方案。 - gce
15个回答

0

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

}


0

有两种解决方案,一种是函数式的,另一种是面向对象的(代码相同),线程安全没有 "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> {}

功能版本:

如果不像上面的示例那样用于最终字段,则直接返回一个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 () -> {
        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;
}

无法强制执行正确的值回调,至少它始终返回相同的结果,但可能无法避免“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();
    }
}

字段值的读取是无序的,但使用易失性memBarrier字段可以确保写入该字段的值的顺序。如果在惰性值被有效设置后调用初始lambda集,则该字段中设置的初始值也将返回初始化的惰性值。

享受


0

这样怎么样。 一些J8功能性的交换来避免每次访问时的if语句。 警告:不支持线程。

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

}

0
类似于Miguel Gamboa的回答,但是使用Optional而不是Supplier。
class A<T> {
    // start empty, first access will set real value
    private Optional<T> fooField = Optional.empty();

    public T getFoo() {
        return fooField.orElseGet(() -> {
            T foo = expensiveInit();
            this.fooField = Optional.ofNullable(foo);
            return foo;
        });
    }
}

-2

这里提供了一个使用Java的代理(反射)和Java 8 Supplier的解决方案。

* 由于使用了代理,所以初始化的对象必须实现传递的接口。

* 与其他解决方案的区别在于将初始化封装在使用中。您可以直接开始使用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));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接