不可变对象的所有属性都必须是最终属性吗?

不可变对象必须具有所有属性吗?final

我会说他们没有。但我不知道我是对的还是错的。


答案 1

不可变对象(所有属性最终)和有效不可变对象(属性不是最终对象,但无法更改)之间的主要区别在于安全发布。

您可以在多线程上下文中安全地发布不可变对象,而不必担心添加同步,这要归功于 Java 内存模型为最终字段提供的保证

final 字段还允许程序员在没有同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变对象,即使使用数据争用在线程之间传递对不可变对象的引用也是如此。这可以提供安全保证,防止不正确或恶意代码滥用不可变类。必须正确使用 final 字段以提供不可变性的保证。

作为旁注,它还允许强制不可变性(如果您尝试在类的未来版本中更改这些字段,因为您忘记了它应该是不可变的,则不会编译)。


澄清

  • 使对象的所有字段最终状态并不使其不可变 - 您还需要确保(i)其状态不能更改(例如,如果对象包含,则在构造后必须完成任何突变操作(添加,删除...)和(ii)在构造过程中不让转义final Listthis
  • 一个有效的不可变对象在安全发布后是线程安全的
  • 不安全发布的示例:

    class EffectivelyImmutable {
        static EffectivelyImmutable unsafe;
        private int i;
        public EffectivelyImmutable (int i) { this.i = i; }
        public int get() { return i; }
    }
    
    // in some thread
    EffectivelyImmutable.unsafe = new EffectivelyImmutable(1);
    
    //in some other thread
    if (EffectivelyImmutable.unsafe != null
        && EffectivelyImmutable.unsafe.get() != 1)
        System.out.println("What???");
    

    该程序理论上可以打印。如果是最终结果,那将不是法律结果。What???i


答案 2

您可以仅通过封装轻松保证不可变性,因此没有必要:

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    public String getBar() {
        return bar;
    }
}

但是,在某些情况下,您还必须通过封装来保证它,因此这还不够

public class Womble {
    private final List<String> cabbages;
    public Womble(List<String> cabbages) {
        this.cabbages = cabbages;
    }
    public List<String> getCabbages() {
        return cabbages;
    }
}
// ...
Womble w = new Womble(...);
// This might count as mutation in your design. (Or it might not.)
w.getCabbages().add("cabbage"); 

这样做是为了捕捉一些微不足道的错误,并清楚地证明你的意图,这不是一个坏主意,但是“所有字段都是最终的”和“类是不可变的”不是等效的陈述。