Lazy field initialization with lambdas

后端 未结 14 2266
日久生厌
日久生厌 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:05

    It's supported,

    By creating a small interface and combining 2 new features introduced in java 8:

    • @FunctionalInterface annotation (allows assigning a lambda on declaration)
    • default keyword (define an implementation, just like abstract class - but in an interface)

    It is possible to get the same Lazy<T> behavior as you seen in C#.


    Usage

    Lazy<String> name = () -> "Java 8";
    System.out.println(name.get());
    

    Lazy.java (copy and paste this interface in somewhere accessible)

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

    Online Example - https://ideone.com/3b9alx

    The following snippet demonstrates the lifecycle of this helper class

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

    will output

    start
    lazy init 1
    name 1
    name 1
    lazy init 2
    name 2
    name 2
    end
    

    See the online demo - https://ideone.com/3b9alx

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

    Well, I don't really suggest having no "if", but here's my take on the matter:

    One simple method is to use an AtomicReference (the ternary operator is still like an "if"):

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

    But then there is the whole thread safety magic that one might not need. So I'd do it like Miguel with a little twist:

    Since I like simple one-liners, I simply use a ternary operator (again, reads like an "if") but I'd let Java's evaluation order do its magic to set the field:

    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's field-modification example above, that works without an "if", can be further simplified too. We don't need the interface. We just need to exploit above "trick" again: An assignment operator's result is the assigned value, we can use brackets to force evaluation order. So with the method above it's just:

    static <T> Supplier<T> value(final T value) {
       return () -> value;
    }
    
    
    Supplier<Point> p2 = () -> (p2 = value(new Point())).get();
    

    Note that you cannot inline the "value(...)" method without losing the laziness.

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

    2 solutions, one functional then and one object (it's same code), thread safe, without "if", and taking care of Exception handling with proper type propagation (no solution here take care about that).

    It is quite short. Better lazy fields support, handled by the runtime, will eventually make this code obsolete...

    usage :

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

    First I define a lambda with exception :

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

    Then a Lazy type :

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

    Functional version :

    It directly returns a lambda that eventually get the less memory footprint, if not used on a final field like in sample above.

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

    One can not enforce to give a correct value callback, at least it always returns the same result but may not avoid the "if" (as in final field case).

    Object version :

    Is fully safe from the outside.

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

    the read of field value is unordered, but use of volatile memBarrier field ensure ordering of value written in this field. The initial lambda set in this field will also returns initialized lazy value if called after the lazy value was effectively set.

    enjoy

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

    You could do something along these lines :

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

    I recently saw this as an idea by Venkat Subramaniam. I copied the code from this page.

    The basic idea is that the Supplier once called, replaces itself with a simpler factory implementation that returns the initialized instance.

    This was in the context of thread safe lazy initialization of a singleton, but you could also apply it to a normal field, obviously.

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

    The approach taken by Miguel Gamboa's answer is a fine one:

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

    It works well for one-off lazy fields. However, if more than one field needs to be initialized this way, the boilerplate would have to be copied and modified. Another field would have to be initialized like this:

    private Supplier<T> barField = () -> {
       T val = expensiveInitBar();          // << changed
       barField = () -> val;                // << changed
       return val;
    };
    

    If you can stand one extra method call per access after the initialization, I'd do it as follows. First, I'd write a higher-order function that returns an instance of Supplier that contains the cached value:

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

    An anonymous class is called for here because it has mutable state, which is the cached of the initialized value.

    Then, it becomes quite easy to create many lazily initialized fields:

    Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
    Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
    Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());
    

    Note: I see in the question that it stipulates "without using an if". It wasn't clear to me whether the concern here is over avoiding the runtime expensive of an if-conditional (really, it's pretty cheap) or whether it's more about avoiding having to repeat the if-conditional in every getter. I assumed it was the latter, and my proposal addresses that concern. If you're concerned about runtime overhead of an if-conditional, then you should also take the overhead of invoking a lambda expression into account.

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

    How about this. Some J8 functional switcheroos to avoid ifs on each access. Warning: not thread aware.

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