有效的不可变对象有意义吗?

在Java Concurrency In Practice一书中,它解释了“有效不可变”对象与可变对象并发性的优点。 但它没有解释“有效不可变”对象在真正不可变对象上提供的优势。

我不明白:在你决定安全地发布一个“有效不可变”的对象的那一刻,难道你不能总是构建一个真正不可变的对象吗? (而不是做你的“安全发布”,你建立一个真正不可变的对象,就是这样)

当我正在设计类时,我无法看到我不能总是构建一个真正不可变对象的情况(如果需要使用委托等来构建其他包装对象,当然它们本身就是真正的不可改变的)我此刻决定“安全地发布“。

那么“有效不可变”的对象和他们的“安全出版物”只是一个糟糕的设计或糟糕的API的案例?

你将被迫使用一个有效的不可变对象,并被迫安全地发布它,你无法建立一个更优越的真正不可变对象?

是的,在某些情况下它们是有意义的。 一个简单的例子是当你想要一些属性被懒惰地生成并缓存时,如果它从未被访问过,你可以避免产生它的开销。 String是一个有效不可变类的示例(使用其哈希码)。

对于循环不可变量:

 class Foo { final Object param; final Foo other; Foo(Object param, Foo other) { this.param = param; this.other = other; } // create a pair of Foo's, A=this, B=other Foo(Object paramA, Object paramB) { this.param = paramA; this.other = new Foo(paramB, this); } Foo getOther(){ return other; } } // usage Foo fooA = new Foo(paramA, paramB); Foo fooB = fooA.getOther(); // publish fooA/fooB (unsafely) 

一个问题是,由于this fooA泄露在构造函数中, fooA仍然是一个线程安全不可变的吗? 也就是说,如果另一个线程读取fooB.getOther().param ,是否可以保证看到paramA ? 答案是肯定的,因为在冻结行动之前, this不会泄露给另一个线程; 我们可以建立spec所需的hb / dc / mc命令来certificateparamA是读取的唯一可见值。

回到原来的问题。 在实践中,总是存在超出纯技术限制的约束。 考虑到所有工程,操作,政治和其他人为原因,初始化构造函数中的所有内容不一定是设计的最佳选择。

曾经想知道为什么我们被认为这是一个伟大的至高无上的想法?

更深层次的问题是Java 缺乏安全发布的一般廉价费用,这比普通的便宜。 Java仅用于final字段; 由于某种原因,除非另有挡板。

现在final有两个独立的含义:第一,最后一个场必须分配一次; 2,安全发布的内存语义。 这两个含义彼此无关。 将它们捆绑在一起非常困惑。 当人们需要第二个含义时,他们也被迫接受第一个含义。 当第一个在设计中非常不方便时,人们想知道他们做错了什么 – 没有意识到它是错误的Java。

在一个final捆绑两个含义使其成为双倍加好,所以显然我们有更多的理由和动力来使用final 。 更阴险的故事实际上是我们被迫使用它,因为我们没有给出更灵活的选择。

使用有效的不可变对象可以避免创建大量的类。 您可以构建一个有效的不可变类,而不是成对[mutable builder] / [immutable object]类。 我通常定义一个不可变的接口,以及一个实现这个接口的可变类。 通过其可变类方法配置对象,然后通过其不可变接口发布。 只要您的库的客户端编程到接口,对于它们,您的对象在其发布的生命周期内保持不变。

假设有一个具有五个属性的不可变类Foo ,名为AlphaBeta等,并且希望提供WithAlphaWithBeta等方法,这些方法将返回与原始实例相同的实例,除非特定属性已更改。 如果该类真正且深入不可变,则该方法必须采用以下forms:

 Foo WithAlpha(string newAlpha)
 { 
  返回新的Foo(newAlpha,Beta,Gamma,Delta,Epsilon);
 }

 Foo WithBeta(字符串newBeta) 
 {
  返回新的Foo(Alpha,NewBeta,Gamma,Delta,Epsilon);
 }

伊克。 大规模违反“不要重复自己”(DRY)的原则。 此外,向类添加新属性需要将其添加到这些方法中的每一个。

另一方面,如果每个Foo拥有一个包含复制构造函数的内部FooGuts ,那么可以执行以下操作:

 Foo WithAlpha(string newAlpha)
 {
   FooGuts newGuts = new FooGuts(Guts);  // Guts是一个私人或受保护的领域
   newGuts.Alpha = newAlpha;
  返回新的Foo(newGuts);  //私有或受保护的构造函数
 }

每个方法的代码行数增加了,但是这些方法不再需要对它们不“感兴趣”的任何属性进行任何引用。注意,如果Foo的构造函数被调用,则Foo可能不是不可变的。存在任何外部引用的FooGuts ,其构造函数只能被代码访问,该代码在构造之后不会维护任何此类引用。