首页 文章

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

提问于
浏览
5

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

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

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

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

5 回答

  • 0

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

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

  • 2

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

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

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

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

  • 0

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

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

  • 6

    你绝对可以拥有一个带有非final字段的不可变对象 .

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

  • 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;
    }
    

相关问题