延迟初始化不可变变量

时间:2010-11-09 14:23:50

标签: java constructor immutability

我一直在使用scala的lazy val成语,我希望在Java中实现类似的功能。我的主要问题是构造一些值我需要一些在对象构造时不知道的其他值,但我不希望以后能够改变它。原因是,我正在使用一个GUI库,代表我实现对象,并在创建所需的所有内容时调用不同的方法,这就是我知道我需要的值。

以下是我尝试实现的属性:
*变量的不变性。
*在构造函数之外的其他方法中初始化。

我认为这在Java中是不可能的,因为只有final实现了变量的不变性,并且final变量无法在构造函数之外初始化。

Java中最接近我想要实现的目标是什么?

2 个答案:

答案 0 :(得分:5)

这样做的一种方法是将所讨论的值的实际实例化推送到另一个类中。这将是最终的,但是在加载类之前不会实际创建,这将延迟到需要时。如下所示:

public class MyClass
{
    private static class Loader
    {
        public static final INSTANCE = new Foo();
    }

    Foo getInstance()
    {
        return Loader.INSTANCE;
    }
}

这将在需要时懒惰地初始化Foo

如果你绝对需要Foo作为顶级课程的实例变量 - 我无法想到任何方式来做这件事。正如您所指出的那样,变量必须填充在构造函数中。

事实上我不确定Scala是如何解决这个问题的,但我的猜测是它将lazy val变量设置为某种 thunk ,它被实际替换首次评估时的对象。 Scala当然可以通过颠覆这种情况下的普通访问修饰符来实现这一点,但我认为你不能透明地在Java中执行此操作。您可以将该字段声明为例如一个Future<Foo>,它在第一次调用时创建值并从该点开始缓存它,但这不是引用透明的,并且根据final的定义,我没有看到解决方法。

答案 1 :(得分:1)

Andrzej's answer很棒,但也有一种方法可以在不更改源代码的情况下完成。使用AspectJ捕获构造函数调用并返回未初始化的对象:

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

鉴于此方面,标有@Slow注释的对象的所有构造函数都将在单独的线程中运行。

我没有找到太多关于链接的参考,但请阅读AspectJ in Action Ramnivas Laddad了解更多信息。