Deferred initialization of immutable variables
I've been using scala's lazy val
idiom a lot and I would like to achieve something similar in Java. My main problem is that to construct some value I need some other value which is not known at object construction time, but I do not want to be able to change it afterwards. The reason for that is, I'm using a GUI library which instanciates the object on my behalf and calls a different method when everything I need is created, which is when I know the values I need.
Her开发者_运维技巧e are the properties I try to achieve:
* Immutability of my variable. * Initialization in some other method than the constructor.I do not think this is possible in Java, for only final
achieves immutability of the variable and final
variables cannot be initialized outside of the constructor.
What would be the closest thing in Java to what I am trying to achieve ?
One way to do it would be to push the actual instantiation of the value in question into another class. This will be final, but won't be actually created until the class is loaded, which is deferred until it is needed. Something like the following:
public class MyClass
{
private static class Loader
{
public static final INSTANCE = new Foo();
}
Foo getInstance()
{
return Loader.INSTANCE;
}
}
This will lazily initialise the Foo
as and when desired.
If you absolutely need the Foo
to be an instance variable of your top-level class - I can't think of any way off-hand to do this. The variable must be populated in the constructor, as you noted.
In fact I'm not sure exactly how Scala gets around this, but my guess would be that it sets the lazy val
variable to some kind of thunk which is replaced by the actual object when first evaluated. Scala can of course do this by subverting the normal access modifiers in this case, but I don't think you can transparently do this in Java. You could declare the field to be e.g. a Future<Foo>
which creates the value on first invocation and caches it from that point on, but that's not referentially transparent, and by the definition of final
I don't see a way around this.
Andrzej's answer is great, but there is also a way to do it without changing the source code. Use AspectJ to capture Constructor invocations and return non-initialized objects:
pointcut lazyInit() : execution(* com.mycompany.expensiveservices.*.init(*));
void around() : lazyInit() && within(@Slow *) {
new Thread(new Runnable(){
@Override
public void run(){
// initialize Object in separate thread
proceed();
}
}
}
Given this aspect, all constructors of objects marked with a @Slow
annotations will be run in a separate thread.
I did not find much reference to link to, but please read AspectJ in Action by Ramnivas Laddad for more info.
精彩评论