代码之家  ›  专栏  ›  技术社区  ›  Yishai

字段是否需要显式结束才能有一个“正确的”不变对象?

  •  5
  • Yishai  · 技术社区  · 14 年前

    在Java中,您经常阅读不需要最后字段的不可变对象。这是事实上的情况,还是仅仅是因为它不具有公开的可变性而实际上不改变状态?

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

    字段最终化具有防止实现错误的明显优势(例如允许代码保留对构建器的引用,并在实际上改变现有对象的同时多次“构建”对象),但是让构建器将其数据存储在对象中,就像构建它一样,这似乎是一种烘干机。

    所以问题是:假设构建器不会过早地泄漏对象,并且在构建后阻止自己修改对象(比如将对象的引用设置为空),如果对象的字段成为最终字段,那么在对象的“不可变性”中是否实际获得了任何东西(比如提高线程安全性)?

    5 回复  |  直到 13 年前
        1
  •  6
  •   erickson    14 年前

    是的,你确实从 final 领域。也就是说,分配给 最终的 施工期间的现场应确保所有螺纹都可见。线程安全的另一种选择是声明字段 volatile 但是,每次阅读都会产生很高的开销……并且会让任何关注您的类的人感到困惑,并奇怪为什么这个“不变”类的字段被标记为“易失性”。

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

        2
  •  2
  •   mikera    14 年前

    对象当然可以有可变的私有字段,并且仍然作为不变的对象工作。要满足不变契约,最重要的是对象从外部看来是不变的。例如,具有非最终私有字段但没有setter的对象将满足此要求。

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

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

    不过,我要补充一点,那就是实际上想要改变不变对象内部结构的地方的数量可能非常少。如果有疑问,请将其定为最终结果。

        3
  •  0
  •   Affe    14 年前

    我认为您只需要考虑它运行的环境,并决定使用反射来操作对象的框架是否是一种危险。

    我们可以很容易地编造一个奇怪的场景,在这个场景中,由于配置为使用反射而不是bean setter的Web绑定框架,假定不变的对象通过注入后攻击而被击倒。

        4
  •  0
  •   Alexander Pogrebnyak    14 年前

    您肯定可以有一个具有非最终字段的不可变对象。

    例如,参见JavaLang.Stand的Java 1.6实现。

        5
  •  0
  •   curiousguy    13 年前

    评论: @埃里克森

    像这样:

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

    ?