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

时间:2012-01-03 02:53:34

标签: java thread-safety immutability

在书Java Concurrency In Practice中,它解释了"有效不可变的优点"对象与可变对象的并发性。但它没有解释什么样的优势"有效的不可改变的"对象将提供真正的不可变对象。

我无法理解:在您决定安全发布“有效”的那一刻, 总是构建一个真正不可改变的对象不可改变"宾语? (而不是做你的"安全的出版物"你构建了一个真正不可改变的对象,那就是它)

当我设计课程时,我没有看到我无法始终构建真正不可变对象的情况(如果需要,可以使用委托等来构建其他包装对象,当然,这些对象本身也是真正的不可改变的)我决定安全发布"。

所以"实际上是不可改变的"对象及其安全出版物"只是一个糟糕的设计或糟糕的API?

你将被迫使用一个有效的不可变对象,并被迫安全地将它发布到你无法构建一个非常优秀的真正不可变对象的地方?

4 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:3)

对于循环不可变:

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的{​​{1}}泄露在构造函数中,fooA仍然是线程安全不可变的吗?也就是说,如果另一个帖子显示fooA,是否可以看到fooB.getOther().param?答案是肯定的,因为在{em>冻结操作之前paramA没有泄露给另一个线程;我们可以建立规范要求的hb / dc / mc订单,以证明this是读取的唯一可见值。

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

有没有想过为什么我们被认为这是一个伟大的至高无上的想法?

更深层次的问题是Java 缺乏安全发布的一般廉价文本,它比volatile更便宜。 Java仅用于paramA个字段;由于某种原因,这个围栏不可用。

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

Foo WithAlpha(string newAlpha)
{ 
  return new Foo(newAlpha, Beta, Gamma, Delta, Epsilon);
}

Foo WithBeta(string newBeta) 
{
  return new Foo(Alpha, NewBeta, Gamma, Delta, Epsilon);
}

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

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

Foo WithAlpha(string newAlpha)
{
  FooGuts newGuts = new FooGuts(Guts); // Guts is a private or protected field
  newGuts.Alpha = newAlpha;
  return new Foo(newGuts); // Private or protected constructor
}

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