Lazy field initialization with lambdas

后端 未结 14 2267
日久生厌
日久生厌 2020-11-29 23:39

I would like to implement lazy field initialization (or deferred initialization) without an if statement and taking advantage of lambdas. So, I would like to have the same b

相关标签:
14条回答
  • 2020-11-30 00:11

    Project Lombok provides a @Getter(lazy = true) annotation which does exactly what you need.

    0 讨论(0)
  • 2020-11-30 00:13

    If you need something that approximates the behaviour of Lazy in C#, which gives you thread safety and a guarantee that you always get the same value, there is no straightforward way to avoid if.

    You will need to use a volatile field and double checked locking. Here is the lowest memory footprint version of a class that gives you the C# behaviour:

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

    It's not that elegant to use. You would have to create lazy values like this:

    final Supplier<Baz> someBaz = new Lazy<Baz>() {
        protected Baz init(){
            return expensiveInit();
        }
    }
    

    You can gain some elegance at the cost of additional memory footprint, by adding a factory method like this:

        public static <V> Lazy<V> lazy(Supplier<V> supplier) {
            return new Lazy<V>() {
                @Override
                protected V init() {
                    return supplier.get();
                }
            };
        }
    

    Now you can create thread safe lazy values simply like this:

    final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
    final Supplier<Bar> lazyBar = lazy(() -> barInit());
    final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
    
    0 讨论(0)
  • 2020-11-30 00:18

    How about this? then you can do something like this by using LazyInitializer from Apache 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();
        }
    }
    
    0 讨论(0)
  • 2020-11-30 00:19

    Within your actual lambda, you can simply update the fooField with a new lambda, such as:

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

    Again this solution is not thread-safe as is the .Net Lazy<T>, and does not ensure that concurrent calls to the getFoo property return the same result.

    0 讨论(0)
  • 2020-11-30 00:21

    Here's a solution using Java's Proxy (reflection) and Java 8 Supplier.

    * Because of the Proxy usage, the initiated object must implement the passed interface.

    * The difference from other solutions is the encapsulation of the initiation from the usage. You start working directly with DataSource as if it was initialized. It will be initialized on the first method's invocation.

    Usage:

    DataSource ds = LazyLoadDecorator.create(() -> initSomeDS(), DataSource.class)
    

    Behind the scenes:

    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));
        }
    }
    
    0 讨论(0)
  • 2020-11-30 00:22

    Stuart Mark's solution, with an explicit class. (Whether this is "better" is a personal preference thing, I think.)

    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 讨论(0)
提交回复
热议问题