我想在没有if语句的情况下实现惰性字段初始化(或延迟初始化)并利用lambdas。所以,我希望以下Foo
属性具有相同的行为,但没有if
:
class A<T>{
private T fooField;
public T getFoo(){
if( fooField == null ) fooField = expensiveInit();
return fooField;
}
}
忽略这个解决方案无法保证安全使用的事实:1)多线程; 2)null
作为T
的有效值。
因此,为了表达fooField
的初始化被推迟到第一次使用的意图,我想声明fooField
类型的Supplier<T>
,例如:
class A<T>{
private Supplier<T> fooField = () -> expensiveInit();
public T getFoo(){
return fooField.get();
}
}
然后在getFoo
属性中我将返回fooField.get()
。但现在我希望getFoo
属性的下一次调用可以避免expensiveInit()
,只返回上一个T
实例。
如何在不使用if
的情况下实现这一目标?
尽管命名约定并将->
替换为=>
,但此示例也可以在C#中考虑。但是,.NET Framework版本4已经为Lazy<T>
提供了所需的语义。
答案 0 :(得分:34)
在您的实际lambda中,您只需使用新的lambda更新fooField
,例如:
class A<T>{
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
public T getFoo(){
return fooField.get();
}
}
同样,此解决方案不像.Net Lazy<T>
那样是线程安全的,并且不能确保对getFoo
属性的并发调用返回相同的结果。
答案 1 :(得分:20)
考虑Miguel Gamboa’s solution并试图在不牺牲其优雅的情况下最小化每个字段的代码,我得出以下解决方案:
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()));
每个字段的代码只比Stuart Marks’s solution略大,但它保留了原始解决方案的nice属性,在第一个查询之后,只有一个轻量级Supplier
无条件地返回已经计算值。
答案 2 :(得分:19)
Miguel Gamboa's answer采用的方法很好:
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
适用于一次性懒惰的田地。但是,如果需要以这种方式初始化多个字段,则必须复制和修改样板。另一个字段必须像这样初始化:
private Supplier<T> barField = () -> {
T val = expensiveInitBar(); // << changed
barField = () -> val; // << changed
return val;
};
如果在初始化后每次访问可以支持一次额外的方法调用,我将按如下方式执行此操作。首先,我写了一个高阶函数,它返回一个包含缓存值的Supplier实例:
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;
}
};
}
这里调用一个匿名类,因为它具有可变状态,即初始化值的缓存。
然后,创建许多延迟初始化的字段变得非常容易:
Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());
注意:我在问题中看到它规定了#34;没有使用if
&#34;。我不清楚这里的关注是否过度避免了if条件的运行时昂贵(实际上,它非常便宜),或者它是否更多地避免重复if - 在每个吸气剂中都是有条件的。我以为是后者,我的建议解决了这个问题。如果您关注if条件的运行时开销,那么您还应该考虑调用lambda表达式的开销。
答案 3 :(得分:10)
Project Lombok提供了@Getter(lazy = true)
注释,可以完全满足您的需求。
答案 4 :(得分:3)
通过创建一个小型界面并结合java 8中引入的两个新功能:
@FunctionalInterface
注释(允许在声明中指定lambda)default
关键字(定义实现,就像抽象类一样 - 但在界面中)可以获得与在C#中看到的相同的Lazy<T>
行为。
用法
Lazy<String> name = () -> "Java 8";
System.out.println(name.get());
Lazy.java (将此界面复制并粘贴到可访问的地方)
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();
}
以下代码段演示了此助手类的生命周期
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");
}
将输出
start
lazy init 1
name 1
name 1
lazy init 2
name 2
name 2
end
答案 5 :(得分:2)
这个怎么样?那么你可以使用Apache Commons的LazyInitializer
来做这样的事情: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();
}
}
答案 6 :(得分:1)
你可以沿着这些方向做点什么:
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();
}
我最近看到这是Venkat Subramaniam的想法。我从this page复制了代码。
基本思想是供应商一旦调用,就会用一个更简单的工厂实现替换它,返回初始化的实例。
这是在单例的线程安全延迟初始化的上下文中,但显然也可以将它应用于普通字段。
答案 7 :(得分:1)
如果您想将参数(在初始化功能界面时没有)传递给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));
}
}
用法与Stuart Marks'回答相同:
private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
答案 8 :(得分:1)
如果你需要一些近似于C#中Lazy
行为的东西,这会给你线程安全并保证你总是得到相同的值,那么就没有简单的方法可以避免if
。 / p>
您需要使用volatile字段并双重检查锁定。这是类的最低内存占用版本,它为您提供了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;
}
}
使用它并不优雅。你必须创建这样的惰性值:
final Supplier<Baz> someBaz = new Lazy<Baz>() {
protected Baz init(){
return expensiveInit();
}
}
通过添加如下工厂方法,您可以以额外的内存占用为代价获得一些优雅:
public static <V> Lazy<V> lazy(Supplier<V> supplier) {
return new Lazy<V>() {
@Override
protected V init() {
return supplier.get();
}
};
}
现在您可以像这样创建线程安全的惰性值:
final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
final Supplier<Bar> lazyBar = lazy(() -> barInit());
final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
答案 9 :(得分:1)
好吧,我并不是真的建议没有&#34;如果&#34;,但这是我对此事的看法:
一种简单的方法是使用AtomicReference(三元运算符仍然像&#34; if&#34;):
private final AtomicReference<Something> lazyVal = new AtomicReference<>();
void foo(){
final Something value = lazyVal.updateAndGet(x -> x != null ? x : expensiveCreate());
//...
}
然而,有一个人可能不需要的整个线程安全魔法。所以我像Miguel那样做了一点点扭曲:
由于我喜欢简单的单行,我只是使用三元运算符(再次,读起来像&#34; if&#34;)但是我让Java的评估顺序发挥其魔力设置字段:
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上面的字段修改示例,如果没有&#34; if&#34;也可以进一步简化。我们不需要界面。我们只需要利用上面的&#34;技巧&#34;再次:赋值运算符的结果是赋值,我们可以使用括号来强制求值。所以使用上面的方法它只是:
我们所需要的只是:
static <T> Supplier<T> value(final T value) {
return () -> value;
}
Supplier<Point> p2 = () -> (p2 = value(new Point())).get();
答案 10 :(得分:0)
这个怎么样? 一些J8功能开关,以避免每次访问ifs。 警告:不知道线程。
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();
}
}
答案 11 :(得分:0)
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());
}
}
答案 12 :(得分:0)
2个解决方案,一个功能,然后一个对象(相同的代码),线程安全,没有“ if” ,并且使用正确的类型传播(这里没有解决办法)。
这很短。由运行时处理的更好的惰性字段支持最终将使此代码过时...
用法:
final Lazy<Integer, RuntimeException> lazyObject = new LazyField<>(() -> 1); // object version : 2 instances (object and lambda)
final Lazy<Integer, RuntimeException> finalFunc = lazyField(() -> value(1)); // less efficient than object : there is one "if" and 2 instances. "null check removal" may remove the if...
private Lazy<Integer, IOException> lazyFunc = lazyField(() -> this.lazyFunc = value(1)); // more efficient than object, 1 instance
private static final Lazy<Integer, AnyException> lazyTest = lazyField(() -> {throw new AnyException();});
首先,我定义一个带有异常的lambda:
@FunctionalInterface
interface SupplierWithException<T, E extends Exception> {
T get() throws E;
}
然后是惰性类型:
interface Lazy<T, E extends Exception> extends SupplierWithException<T, E> {}
功能版本:
它直接返回一个lambda,如果不像上面的示例中那样在最终字段上使用,则最终将获得较少的内存占用。
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> value(T value) {
return () -> value;
}
一个人不能强制给出正确的值回调,至少它总是返回相同的结果,但不能避免“ if”(如在最终字段中那样)。
对象版本:
从外面是完全安全的。
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 = value(supplier.get()));
}
@Override
public T get() throws E {
return value.get();
}
}
享受
答案 13 :(得分:-1)
这是使用Java的代理(反射)和Java 8供应商的解决方案。
*由于代理使用,启动的对象必须实现传递的接口。
*与其他解决方案的不同之处在于从使用中封装了启动。您开始直接使用DataSource
,就像它已初始化一样。它将在第一个方法的调用时初始化。
<强>用法:强>
DataSource ds = LazyLoadDecorator.create(() -> initSomeDS(), DataSource.class)
在幕后:
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));
}
}