字段是否需要显式最终才能拥有“适当的”不可变对象?

时间:2010-05-17 22:43:29

标签: java immutability

您经常阅读有关不可变对象的信息,这些对象要求最终字段在Java中不可变。实际上是这种情况,还是仅仅没有公开可变性而实际上没有改变状态?

例如,如果你有一个由构建器模式构建的不可变对象,你可以通过让构建器在构建时分配各个字段,或让构建器自己保存字段并最终通过传递返回不可变对象来实现。它的(私有)构造函数的值。

使字段最终具有防止实现错误的明显优势(例如允许代码保留对构建器的引用和多次“构建”对象,而实际上是在改变现有对象),但让Builder存储它构建对象内部的数据似乎是DRYer。

所以问题是:假设Builder没有提前泄漏Object并且一旦构建就停止修改对象(例如通过将对象的引用设置为null),实际上是获得了什么(例如改进的线程安全性)如果对象的字段是最终的,那么在对象的“不变性”中呢?

5 个答案:

答案 0 :(得分:6)

是的,您确实从final字段获得了“线程安全”。也就是说,在构造期间分配给final字段的值保证对所有线程可见。线程安全的另一个替代方法是声明字段volatile,但是每次读取都会产生很高的开销......并且会让看到你的类的人感到困惑并且想知道为什么这个“不可变”类的字段被标记了“挥发”。

在技术上标记字段final是最正确的,并且最清楚地传达您的意图。不幸的是,它确实使构建器模式非常麻烦。我认为应该可以创建一个注释处理器来合成一个不可变类的构建器,就像Project Lombok对setter和getter一样。真正的工作是需要IDE支持,以便您可以对不存在的构建器进行编码。

答案 1 :(得分:2)

一个Object当然可以拥有可变的私有字段,并且仍然可以作为不可变对象。满足不变性合同的重要性在于,对象看起来与外界不可变。具有非最终私有字段但没有setter的对象将例如满足此要求。

事实上,如果你的封装是正确的,那么你实际上可以改变内部状态并仍然成功地作为“不可变”对象运行。一个例子可能是某种懒惰的评估或数据结构的缓存。

例如,Clojure在其惰性序列的内部实现中执行此操作,这些对象的行为就好像它们是不可变的,但实际上只在直接请求时计算和存储未来值。任何后续请求都会检索存储的值。

但是 - 我想补充一点,即实际上想要改变不可变对象内部的地方数量可能非常少见。如果有疑问,请将它们作为最终版本。

答案 2 :(得分:0)

我认为您只需要考虑其运行的环境,并确定使用反射来操纵对象的框架是否存在危险。

由于一个配置为使用反射而不是bean设置器的Web绑定框架,人们可以很容易地制造一个奇怪的场景,其中一个假定的不可变对象通过POST注入攻击被破坏。

答案 3 :(得分:0)

你肯定可以拥有一个带有非最终字段的不可变对象。

例如,请参阅java.lang.String的java 1.6实现。

答案 4 :(得分:0)

注释: @erickson

就像那样:

class X { volatile int i, j; }
X y;

// thread A: 
X x = new X;
x.i = 1;
x.j = 2;
y = x;

// thread B: 
if (y != null) {
    a = y.i; 
    b = y.j;
}