lambdas - java 8 stream example
Inicialización de campo diferido con lambdas (14)
Me gustaría implementar la inicialización de campo diferido (o inicialización diferida) sin una declaración if y aprovechando las lambdas.
Entonces, me gustaría tener el mismo comportamiento de la siguiente propiedad de
Foo
pero sin el
if
:
class A<T>{
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) subprocesos múltiples;
2)
null
como valor válido de
T
Entonces, para expresar la intención de que la inicialización de
fooField
se difiera hasta su primer uso, me gustaría declarar el
fooField
del tipo
Supplier<T>
como:
class A<T>{
private Supplier<T> fooField = () -> expensiveInit();
public T getFoo(){
return fooField.get();
}
}
y luego en la propiedad
getFoo
simplemente devolvería
fooField.get()
.
Pero ahora quiero que las próximas invocaciones para
getFoo
propiedad
getFoo
eviten el
getFoo
expensiveInit()
y solo devuelvan la instancia
T
anterior.
¿Cómo puedo lograr eso sin usar un
if
?
A pesar de las convenciones de nomenclatura y la sustitución de
->
por
=>
, este ejemplo también podría considerarse en C #.
Sin embargo, NET Framework versión 4 ya proporciona un
Lazy<T>
con la semántica deseada.
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 declaración) -
palabra clave
default
(define una implementación, al igual que la clase abstracta, pero en una interfaz)
Es posible obtener
el mismo comportamiento
Lazy<T>
que viste en C #.
Uso
Lazy<String> name = () -> "Java 8";
System.out.println(name.get());
Lazy.java (copie y pegue esta interfaz en un lugar accesible)
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();
}
Ejemplo en línea: https://ideone.com/3b9alx
El siguiente fragmento muestra el ciclo de vida de esta clase auxiliar
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");
}
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 puede 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<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();
}
}
2 soluciones, una funcional entonces y un objeto (es el mismo código), seguro para subprocesos , sin "si" , y cuidando el manejo de excepciones con la propagación de tipo adecuada (ninguna solución aquí se ocupa de eso).
Es bastante corto. Un mejor soporte de campos perezosos, manejado por el tiempo de ejecución, eventualmente hará que este código sea obsoleto ...
uso:
// 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 and one "if". null check removal may remove the "if"...
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();});
Primero defino una lambda con excepción:
@FunctionalInterface
interface SupplierWithException<T, E extends Exception> {
T get() throws E;
}
Entonces un tipo perezoso:
interface Lazy<T, E extends Exception> extends SupplierWithException<T, E> {}
Versión funcional:
Devuelve directamente una lambda que eventualmente obtiene la menor huella de memoria, si no se usa en un campo final como en la muestra anterior.
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 () -> {
if(field[0] == null) {
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;
}
No se puede exigir que se devuelva la llamada con un valor correcto, al menos siempre devuelve el mismo resultado pero no se puede evitar el "si" (como en el caso de campo final).
Versión del objeto:
Es totalmente seguro desde el exterior.
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(() -> value = eval(supplier.get()));
}
@Override
public T get() throws E {
return value.get();
}
}
disfrutar
Aquí hay una manera 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<T> {
private Function<Supplier<? extends T>, T> supplier;
private Cache(){
supplier = s -> {
T value = s.get();
supplier = n -> value;
return value;
};
}
public static <T> Supplier<T> of(Supplier<? extends T> creater){
Cache<T> c = new Cache<>();
return () -> c.supplier.apply(creater);
}
public static <T, U> Function<U, T> of(Function<? super U, ? extends T> creater){
Cache<T> c = new Cache<>();
return u -> c.supplier.apply(() -> creater.apply(u));
}
public static <T, U, V> BiFunction<U, V, T> of(BiFunction<? super U, ? super V, ? extends T> creater){
Cache<T> 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<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
Aquí hay una solución que utiliza el Proxy (reflexión) de Java y el Proveedor Java 8.
* Debido al uso de Proxy, el objeto iniciado debe implementar la interfaz pasada.
* 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<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));
}
}
Bueno, realmente no sugiero no tener "si", pero aquí está mi opinión sobre el asunto:
Un método simple es usar una referencia atómica (el operador ternario sigue siendo como un "si"):
private final AtomicReference<Something> lazyVal = new AtomicReference<>();
void foo(){
final Something value = lazyVal.updateAndGet(x -> x != null ? x : expensiveCreate());
//...
}
Pero luego está toda la magia de seguridad del hilo 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 un "si") pero dejaría que el orden de evaluación de Java haga su magia para establecer el campo:
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());
}
};
}
El ejemplo de modificación de campo de gerardw anterior, que funciona sin un "si", se puede simplificar aún más. No necesitamos la interfaz. Solo necesitamos volver a explotar el "truco" anterior: el resultado de un operador de asignación es el valor asignado, podemos usar paréntesis para forzar el orden de evaluación. Entonces, con el método anterior es solo:
static <T> Supplier<T> value(final T value) {
return () -> value;
}
Supplier<Point> p2 = () -> (p2 = value(new Point())).get();
Tenga en cuenta que no puede incorporar el método "valor (...)" sin perder la pereza.
Dentro de su lambda real, simplemente puede actualizar
fooField
con una nueva lambda, como:
class A<T>{
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
public T getFoo(){
return fooField.get();
}
}
Una vez más, esta solución no es segura para subprocesos como lo es .Net
Lazy<T>
, y no garantiza que las llamadas simultáneas a la propiedad
getFoo
devuelvan el mismo resultado.
El enfoque adoptado por la respuesta de Miguel Gamboa es bueno:
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
Funciona bien para campos perezosos únicos. Sin embargo, si es necesario inicializar más de un campo de esta manera, la placa repetitiva tendría que copiarse y modificarse. Otro campo tendría que ser inicializado así:
private Supplier<T> barField = () -> {
T val = expensiveInitBar(); // << changed
barField = () -> val; // << changed
return val;
};
Si puede soportar una llamada de método adicional 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 devuelva una instancia de Proveedor que contenga el valor almacenado en caché:
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;
}
};
}
Aquí se solicita 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 vagamente inicializados:
Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());
Nota: Veo en la pregunta que estipula "sin usar un
if
".
No estaba claro para mí si la preocupación aquí es evitar el costoso tiempo de ejecución de un if-conditional (realmente, es bastante barato) o si se trata más de evitar tener que repetir el if-conditional en cada getter.
Asumí que era lo último, y mi propuesta aborda esa preocupación.
Si le preocupa la sobrecarga de tiempo de ejecución de un if-conditional, también debe tener en cuenta la sobrecarga de invocar una expresión lambda.
La solución de Stuart Mark, con una clase explícita. (Creo que si esto es "mejor" es una cuestión de preferencia personal).
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());
}
}
Podrías hacer algo en este sentido:
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 Subramaniam. 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 diferida segura de subprocesos de un singleton, pero obviamente también podría aplicarse a un campo normal.
Qué tal esto. Algunos conmutadores funcionales J8 para evitar ifs en cada acceso. Advertencia: no reconoce el hilo.
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();
}
}
Si necesita algo que se aproxime al comportamiento de
Lazy
en C #, que le brinda seguridad de subprocesos y una garantía de que siempre obtendrá el mismo valor, no hay forma directa de evitarlo.
Deberá utilizar un campo volátil y un bloqueo doblemente verificado. 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<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;
}
}
No es tan elegante de usar. Tendría que crear valores perezosos como este:
final Supplier<Baz> someBaz = new Lazy<Baz>() {
protected Baz init(){
return expensiveInit();
}
}
Puede obtener algo de elegancia a costa de una huella de memoria adicional, agregando un método de fábrica como este:
public static <V> Lazy<V> lazy(Supplier<V> supplier) {
return new Lazy<V>() {
@Override
protected V init() {
return supplier.get();
}
};
}
Ahora puede crear valores flojos seguros para subprocesos simplemente así:
final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
final Supplier<Bar> lazyBar = lazy(() -> barInit());
final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
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<T> extends Supplier<T> {
Supplier<T> init();
public default T get() { return init().get(); }
}
static <U> Supplier<U> lazily(Lazy<U> lazy) { return lazy; }
static <T> Supplier<T> value(T value) { return ()->value; }
Supplier<Baz> fieldBaz = lazily(() -> fieldBaz=value(expensiveInitBaz()));
Supplier<Goo> fieldGoo = lazily(() -> fieldGoo=value(expensiveInitGoo()));
Supplier<Eep> fieldEep = lazily(() -> fieldEep=value(expensiveInitEep()));
El código por campo solo es un poco más grande 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
ligero que devuelva incondicionalmente el valor ya calculado.
Project Lombok
proporciona una
@Getter(lazy = true)
que hace exactamente lo que necesita.