Java 使用函数编程的延迟初始化

Java 使用函数编程的延迟初始化,java,functional-programming,Java,Functional Programming,让我们定义一个类,使多个实例变量只能延迟初始化一次,如下所示: public class MyClass { private Object myLazyField; private Integer anotherLazyField; public Object getMyLazyField() { if (myLazyField == null) { synchronized (this) { if

让我们定义一个类,使多个实例变量只能延迟初始化一次,如下所示:

public class MyClass {
    private Object myLazyField;
    private Integer anotherLazyField;

    public Object getMyLazyField() {
        if (myLazyField == null) {
            synchronized (this) {
                if (myLazyField == null) {
                    myLazyField = new Object();
                }
            }
        }
        return myLazyField;
    }

    public Integer getAnotherLazyField() {
        if (anotherLazyField == null) {
            synchronized (this) {
                if (anotherLazyField == null) {
                    anotherLazyField = 10;
                }
            }
        }
        return anotherLazyField;
    }
}
代码非常难看,初始化的结构是重复的

我的问题是:如何简化延迟初始化,以避免结构重复?

我试过这个:

class Utility {
    public static <T> T init(Object object, T initialValue, Supplier<T> supplier) {
        synchronized (object) {
            if (initialValue == null) {
                return supplier.get();
            }
        }
        return initialValue;
    }
}

它更好,但我仍然在寻找使用函数式编程的更好解决方案。

我有一个
懒惰的
类,我在我的个人项目中使用了很多。第一,用法:

public class MyClass {
    private final Lazy<Object> myLazyField = new Lazy<>(Object::new);
    private final Lazy<Integer> anotherLazyField = new Lazy<>(() -> 10);

    public Object getMyLazyField() { return myLazyField.get(); }
    public Integer getAnotherLazyField() { return anotherLazyField.get(); }
}

†维基百科文章解释了原因:

直观地说,该算法似乎是问题的有效解决方案。然而,这种技术有许多微妙的问题,通常应该避免。例如,考虑以下事件序列:

  • 线程A注意到该值未初始化,因此它获得锁并开始初始化该值
  • 由于某些编程语言的语义,允许编译器生成的代码在完成初始化之前更新共享变量以指向部分构造的对象。例如,在Java中,如果对构造函数的调用已内联,那么一旦分配了存储,共享变量可能会在内联构造函数初始化对象之前立即更新
  • 线程B注意到共享变量已经初始化(或者看起来是这样),并返回其值。因为线程B认为该值已经初始化,所以它不会获取锁。如果B在看到A完成的所有初始化之前使用该对象(可能是因为A尚未完成初始化,或者是因为对象中的一些初始化值尚未渗透到B使用的内存()),则程序可能会崩溃

  • 使用
    volatile
    修复了上述问题,并使其能够安全地在Java 1.5+中使用。

    我有一个
    Lazy
    类,我在个人项目中经常使用它。第一,用法:

    public class MyClass {
        private final Lazy<Object> myLazyField = new Lazy<>(Object::new);
        private final Lazy<Integer> anotherLazyField = new Lazy<>(() -> 10);
    
        public Object getMyLazyField() { return myLazyField.get(); }
        public Integer getAnotherLazyField() { return anotherLazyField.get(); }
    }
    

    †维基百科文章解释了原因:

    直观地说,该算法似乎是问题的有效解决方案。然而,这种技术有许多微妙的问题,通常应该避免。例如,考虑以下事件序列:

  • 线程A注意到该值未初始化,因此它获得锁并开始初始化该值
  • 由于某些编程语言的语义,允许编译器生成的代码在完成初始化之前更新共享变量以指向部分构造的对象。例如,在Java中,如果对构造函数的调用已内联,那么一旦分配了存储,共享变量可能会在内联构造函数初始化对象之前立即更新
  • 线程B注意到共享变量已经初始化(或者看起来是这样),并返回其值。因为线程B认为该值已经初始化,所以它不会获取锁。如果B在看到A完成的所有初始化之前使用该对象(可能是因为A尚未完成初始化,或者是因为对象中的一些初始化值尚未渗透到B使用的内存()),则程序可能会崩溃

  • 使用
    volatile
    修复了上述问题,并使其在Java 1.5+中使用更加安全。

    我不明白为什么您会觉得第一个解决方案很难看。当然,重复的代码语句
    if(anotherLazyField==null){
    可以被删除。除此之外,它只有3行代码。你想概括多个字段的惰性初始化吗?我不是Java注释处理专家,但我相信你可以编写一个处理器,用适当的注释自动为getter方法添加惰性。类似于不可变或自动值。@dash-这绝对是个好主意。我不明白你为什么觉得第一个解决方案很难看。当然,重复的代码语句
    if(anotherLazyField==null){
    可以被删除。除此之外,它只有3行代码。你想概括多个字段的惰性初始化吗?我不是Java注释处理专家,但我相信你可以编写一个处理器,用适当的注释自动为getter方法添加惰性。类似于不可变或自动值。@dash-o这绝对是个好主意。谢谢你的回答。我知道
    volatile
    技巧(IntelliJ抱怨在运行代码检查时缺少volatile)。我认为这是一个好主意。我唯一的反对意见是关于
    对象:当多个线程执行代码时,可能会创建多个
    惰性
    实例,并且锁无法正常工作(除非我出了问题)。您能提供一个如何在我的代码中使用它的示例吗?我看到的问题是,我必须像您一样将
    惰性
    实例声明为实例变量,我不能将它们用作方法变量,直接在getter中定义。除此之外,我觉得一切都很好。谢谢您的回答。我知道
    volatile
    技巧(IntelliJ抱怨在运行代码检查时缺少volatile)。我认为这是一个好主意。我唯一的反对意见是关于
    lock
    对象:当多个线程执行代码时,可能会创建多个
    Lazy
    实例,并且锁定无法正常工作(除非我出错)。您能提供一个如何在我的代码中使用它的示例吗?我看到的问题是,我必须像您一样将
    惰性
    实例声明为实例变量,我不能将它们用作方法变量,直接在getter中定义。除此之外,我觉得一切都很好。
    import java.util.*;
    import java.util.function.*;
    
    /**
     * A lazy-loaded value that is only created when the value is required. If the
     * value is never used, possibly expensive initialization (either in time or in
     * memory usage) is avoided.
     * <p>
     * This class is thread-safe. Initialization will only ever be performed once,
     * and it is safe to call {@link #get()} simultaneously from different threads.
     * <p>
     * {@code Lazy} makes implementing singletons simple by handling the deferred
     * initialization logic for you. It can also be used as a simple form of caching
     * for expensive computations.
     */
    public final class Lazy<T> {
        private volatile T value;
        private volatile Supplier<T> factory;
        private final Object lock = new Object();
    
        /** Create a lazy object that gets its value from the supplied factory. */
        public Lazy(Supplier<T> factory) {
            this.value = null;
            this.factory = factory;
        }
    
        /**
         * Create a lazy object that holds the given value. This constructor can be
         * used if you happen to have already computed the value.
         */
        public Lazy(T value) {
            this.value = value;
            this.factory = null;
        }
    
        /** Get the value. If this is the first call, the value is initialized. */
        public T get() {
            // The double-checked locking idiom is safe in Java when the tested variable is
            // volatile, which `factory` is.
            if (factory != null) {
                synchronized (lock) {
                    if (factory != null) {
                        value = factory.get();
                        factory = null;
                    }
                }
            }
    
            return value;
        }
    
        @Override
        public String toString() {
            return Objects.toString(get());
        }
    
        @Override
        public boolean equals(Object object) {
            return (object instanceof Lazy) && Objects.equals(get(), ((Lazy) object).get());
        }
    
        @Override
        public int hashCode() {
            return Objects.hashCode(get());
        }
    }