您经常阅读有关不可变对象的信息,这些对象要求最终字段在Java中不可变。实际上是这种情况,还是仅仅没有公开可变性而实际上没有改变状态?
例如,如果你有一个由构建器模式构建的不可变对象,你可以通过让构建器在构建时分配各个字段,或让构建器自己保存字段并最终通过传递返回不可变对象来实现。它的(私有)构造函数的值。
使字段最终具有防止实现错误的明显优势(例如允许代码保留对构建器的引用和多次“构建”对象,而实际上是在改变现有对象),但让Builder存储它构建对象内部的数据似乎是DRYer。
所以问题是:假设Builder没有提前泄漏Object并且一旦构建就停止修改对象(例如通过将对象的引用设置为null),实际上是获得了什么(例如改进的线程安全性)如果对象的字段是最终的,那么在对象的“不变性”中呢?
答案 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; }