Inicialización de campo diferido con lambdas

Me gustaría implementar la inicialización de campo diferido (o la inicialización diferida) sin una instrucción if y aprovechar lambdas. Por lo tanto, me gustaría tener el mismo comportamiento de la siguiente propiedad Foo pero sin el if :

 class A{ private T fooField; public T getFoo(){ if( fooField == null ) fooField = expensiveInit(); return fooField; } } 

Ignore el hecho de que esta solución no garantiza un uso seguro para: 1) multi-threading; 2) null como un valor válido de T

Entonces, para express la intención de fooField la inicialización de fooField hasta su primer uso, me gustaría declarar el fooField del tipo Supplier como:

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

y luego en la propiedad getFoo solo devolvería fooField.get() . Pero ahora quiero que las siguientes invocaciones a la propiedad getFoo eviten el expensiveInit() y simplemente devuelvan la instancia T anterior.

¿Cómo puedo lograr eso sin usar un if ?

A pesar de nombrar convenciones y reemplazar el -> por => , entonces este ejemplo también podría considerarse en C #. Sin embargo, NET Framework versión 4 ya proporciona un Lazy con la semántica deseada.

    Dentro de su lambda real, simplemente puede actualizar fooField con una nueva lambda, como por ejemplo:

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

    De nuevo, esta solución no es segura para subprocesos, como lo es .Net Lazy , y no garantiza que las llamadas simultáneas a la propiedad getFoo devuelvan el mismo resultado.

    Tomando la solución de Miguel Gamboa y tratando de minimizar el código por campo sin sacrificar su elegancia, llegué a la siguiente solución:

     interface Lazy extends Supplier { Supplier init(); public default T get() { return init().get(); } } static  Supplier lazily(Lazy lazy) { return lazy; } static  Supplier value(T value) { return ()->value; } Supplier fieldBaz = lazily(() -> fieldBaz=value(expensiveInitBaz())); Supplier fieldGoo = lazily(() -> fieldGoo=value(expensiveInitGoo())); Supplier fieldEep = lazily(() -> fieldEep=value(expensiveInitEep())); 

    El código por campo es solo un poco mayor que en la solución de Stuart Marks, pero conserva la buena propiedad de la solución original de que, después de la primera consulta, solo habrá un Supplier liviano que devuelva incondicionalmente el valor ya calculado.

    El enfoque adoptado por la respuesta de Miguel Gamboa es bueno:

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

    Funciona bien para campos perezosos únicos. Sin embargo, si más de un campo necesita ser inicializado de esta manera, el texto estándar tendría que ser copiado y modificado. Otro campo debería inicializarse así:

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

    Si puede soportar una llamada adicional al método por acceso después de la inicialización, lo haría de la siguiente manera. Primero, escribiría una función de orden superior que devuelve una instancia de Proveedor que contiene el valor en caché:

     static  Supplier lazily(Supplier supplier) { return new Supplier() { Z value; // = null @Override public Z get() { if (value == null) value = supplier.get(); return value; } }; } 

    Aquí se llama a una clase anónima porque tiene un estado mutable, que es el almacenamiento en caché del valor inicializado.

    Entonces, se vuelve bastante fácil crear muchos campos inicializados perezosamente:

     Supplier fieldBaz = lazily(() -> expensiveInitBaz()); Supplier fieldGoo = lazily(() -> expensiveInitGoo()); Supplier fieldEep = lazily(() -> expensiveInitEep()); 

    Nota: Veo en la pregunta que estipula "sin usar un if ". No estaba claro si la preocupación aquí es evitar el tiempo de ejecución caro de un if-conditional (en realidad, es bastante barato) o si se trata más de evitar tener que repetir el if-conditional en cada getter. Supuse que era lo último, y mi propuesta aborda esa preocupación. Si le preocupa la sobrecarga en tiempo de ejecución de un if-conditional, entonces también debe tomar en cuenta la sobrecarga de invocar una expresión lambda.

    El Proyecto Lombok proporciona una @Getter(lazy = true) que hace exactamente lo que necesita.

    Es compatible,

    Al crear una interfaz pequeña y combinar 2 nuevas características introducidas en java 8:

    • Anotación @FunctionalInterface (permite asignar una lambda en la statement)
    • palabra clave default (definir una implementación, al igual que la clase abstracta, pero en una interfaz)

    Es posible obtener el mismo comportamiento Lazy que se ve en C #.


    Uso

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

    Lazy.java (copiar y pegar esta interfaz en algún lugar accesible)

     import java.util.function.Supplier; @FunctionalInterface public interface Lazy extends Supplier { abstract class Cache { private volatile static Map instances = new HashMap<>(); private static synchronized Object getInstance(int instanceId, Supplier 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(); } 

    Ejemplo en línea – https://ideone.com/3b9alx

    El siguiente fragmento muestra el ciclo de vida de esta clase de ayuda

     static Lazy name1 = () -> { System.out.println("lazy init 1"); return "name 1"; }; static Lazy 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"); } 

    saldrá

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

    Vea la demostración en línea – https://ideone.com/3b9alx

    ¿Qué tal esto? entonces puedes hacer algo como esto usando LazyInitializer de Apache Commons: https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/concurrent/LazyInitializer.html

     private static Lazy _lazyDouble = new Lazy<>(()->1.0); class Lazy extends LazyInitializer { private Supplier builder; public Lazy(Supplier builder) { if (builder == null) throw new IllegalArgumentException(); this.builder = builder; } @Override protected T initialize() throws ConcurrentException { return builder.get(); } } 

    Podrías hacer algo en esta línea:

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

    Hace poco vi esto como una idea de Venkat Subtwigniam. Copié el código de esta página .

    La idea básica es que el Proveedor una vez llamado, se reemplaza con una implementación de fábrica más simple que devuelve la instancia inicializada.

    Esto fue en el contexto de la inicialización lenta y segura de un singleton, pero también se puede aplicar a un campo normal, obviamente.

    Esta es una forma que también funciona si desea pasar argumentos (que no tiene al inicializar la interfaz funcional) a su método expensiveInit .

     public final class Cache { private Function, T> supplier; private Cache(){ supplier = s -> { T value = s.get(); supplier = n -> value; return value; }; } public static  Supplier of(Supplier< ? extends T> creater){ Cache c = new Cache<>(); return () -> c.supplier.apply(creater); } public static  Function of(Function< ? super U, ? extends T> creater){ Cache c = new Cache<>(); return u -> c.supplier.apply(() -> creater.apply(u)); } public static  BiFunction of(BiFunction< ? super U, ? super V, ? extends T> creater){ Cache c = new Cache<>(); return (u, v) -> c.supplier.apply(() -> creater.apply(u, v)); } } 

    El uso es el mismo que la respuesta de Stuart Marks :

     private final Function lazyBar = Cache.of(this::expensiveBarForFoo); 

    Qué tal esto. Algunos switcheroos funcionales J8 para evitar ifs en cada acceso. Advertencia: no ser consciente de los hilos.

     import java.util.function.Supplier; public class Lazy { private T obj; private Supplier creator; private Supplier fieldAccessor = () -> obj; private Supplier initialGetter = () -> { obj = creator.get(); creator = null; initialGetter = null; getter = fieldAccessor; return obj; }; private Supplier getter = initialGetter; public Lazy(Supplier creator) { this.creator = creator; } public T get() { return getter.get(); } } 

    Si necesita algo que se aproxime al comportamiento de Lazy en C #, que le ofrece seguridad en el hilo y una garantía de que siempre obtiene el mismo valor, no hay una manera directa de evitarlo.

    Tendrá que usar un campo volátil y un locking comprobado doble. Aquí está la versión de huella de memoria más baja de una clase que le da el comportamiento de C #:

     public abstract class Lazy implements Supplier { 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; } } 

    No es tan elegante de usar. Tendría que crear valores perezosos como este:

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

    Puede ganar algo de elegancia a costa de una memoria adicional, agregando un método de fábrica como este:

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

    Ahora puede crear valores perezosos seguros para subprocesos simplemente así:

     final Supplier lazyFoo = lazy(() -> fooInit()); final Supplier lazyBar = lazy(() -> barInit()); final Supplier lazyBaz = lazy(() -> bazInit()); 

    La solución de Stuart Mark, con una clase explícita. (Si esto es “mejor” es una cuestión de preferencia personal, creo).

     public class ScriptTrial { static class LazyGet implements Supplier { private T value; private Supplier supplier; public LazyGet(Supplier supplier) { value = null; this.supplier = supplier; } @Override public T get() { if (value == null) value = supplier.get(); return value; } } Supplier lucky = new LazyGet<>(()->seven()); int seven( ) { return 7; } @Test public void printSeven( ) { System.out.println(lucky.get()); System.out.println(lucky.get()); } 

    }

    Bueno, realmente no sugiero no tener “si”, pero aquí está mi opinión sobre el asunto:

    Un método simple es usar una AtomicReference (el operador ternario sigue siendo como un “si”):

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

    Pero luego está todo el hilo de la magia de seguridad que uno podría no necesitar. Entonces lo haría como Miguel con un pequeño giro:

    Como me gustan las frases simples, simplemente uso un operador ternario (de nuevo, se lee como “si”) pero dejo que el orden de evaluación de Java haga su magia para establecer el campo:

     public static  Supplier lazily(final Supplier supplier) { return new Supplier() { private T value; @Override public T get() { return value != null ? value : (value = supplier.get()); } }; } 

    El ejemplo anterior de modificación de campo de gerardw, que funciona sin un “si”, se puede simplificar aún más. No necesitamos la interfaz. Solo necesitamos explotar el “truco” anterior: el resultado de un operador de asignación es el valor asignado, podemos usar corchetes para forzar el orden de evaluación. Entonces con el método de arriba es solo:

    Todo lo que necesitamos es esto:

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

    Aquí hay una solución que usa Proxy (reflection) y Java 8 Supplier de Java.

    * Debido al uso del Proxy, el objeto iniciado debe implementar la interfaz aprobada.

    * La diferencia con otras soluciones es la encapsulación de la iniciación del uso. Comienza a trabajar directamente con DataSource como si se hubiera inicializado. Se inicializará en la invocación del primer método.

    Uso:

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

    Entre bastidores:

     public class LazyLoadDecorator implements InvocationHandler { private final Object syncLock = new Object(); protected volatile T inner; private Supplier supplier; private LazyLoadDecorator(Supplier 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 create(Supplier supplier, Class clazz) { return (T) Proxy.newProxyInstance(LazyLoadDecorator.class.getClassLoader(), new Class[] {clazz}, new LazyLoadDecorator<>(supplier)); } }