可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 behavior of the following Foo
property but without the if
:
class A<T>{
private T fooField;
public T getFoo(){
if( fooField == null ) fooField = expensiveInit();
return fooField;
}
}
Ignore the fact that this solution is not guaranteeing safe use for: 1) multi-threading; 2) null
as a valid value of T
.
So, to express the intention that the initialization of the fooField
is deferred until its first use I would like to declare the fooField
of the type Supplier<T>
such as:
class A<T>{
private Supplier<T> fooField = () -> expensiveInit();
public T getFoo(){
return fooField.get();
}
}
and then in the getFoo
property I would just return fooField.get()
. But now I want that next invocations to getFoo
property avoid the expensiveInit()
and just return the previous T
instance.
How can I achieve that without using an if
?
Despite naming conventions and replacing the ->
by =>
, then this example could be also considered in C#. However, NET Framework version 4 already provides a Lazy<T>
with the desired semantics.
回答1:
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.
回答2:
Taking Miguel Gamboa’s solution and trying to minimize the per-field code without sacrificing its elegance, I came to the following 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()));
The per-field code only slightly bigger than in Stuart Marks’s solution but it retains the nice property of the original solution that after the first query, there will be only a lightweight Supplier
which unconditionally returns the already computed value.
回答3:
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.
回答4:
Project Lombok provides a @Getter(lazy = true)
annotation which does exactly what you need.
回答5:
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
回答6:
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();
}
}
回答7:
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.
回答8:
Here's a way that also works if you want to pass arguments (which you dont have when initializing the functional interface) to your expensiveInit
method.
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));
}
}
Usage is the same as Stuart Marks' answer:
private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
回答9:
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());
回答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();
}
}
回答11:
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());
}
}
回答12:
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:
All we need is this:
static <T> Supplier<T> value(final T value) {
return () -> value;
}
Supplier<Point> p2 = () -> (p2 = value(new Point())).get();
回答13:
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));
}
}