Java不可变对象

时间:2013-07-18 08:19:43

标签: java string object wrapper immutability

我正在学习不变性的概念。

我知道一旦创建了对象,不可变对象就无法更改它们的值。

但我不理解以下对不可变对象的使用。

他们是

  • 是自动线程安全的,没有同步问题。 How ? Proof ?
  • 不需要复制构造函数。 How ? Any example ?
  • 不需要克隆How ? Any example ?
  • 的实现
  • 用作字段How ? Any example ?
  • 时,无需进行防御性复制
  • 总是有"failure atomicity" (a term used by Joshua Bloch):如果一个不可变对象抛出异常,它永远不会处于不受欢迎或不确定的状态。 How ? Any example ?

有人可以通过支持它的示例解释 详细 中的每一点吗?

感谢。

4 个答案:

答案 0 :(得分:10)

  

..是自动线程安全的,没有同步问题

当两个不同的线程修改同一对象的状态时,会发生并发问题。不可修改的对象无法修改,因此没有问题。

示例:A String。两个线程可以毫无顾虑地传递给同一个String,因为它们都不能以任何方式突变它

  

不需要复制构造函数

...因为复制是改变它的唯一方法。每个“修改”操作的不可变对象的一种常见设计模式,用于制作副本,然后对新对象执行操作。

复制构造函数通常用于要更改的对象,而不会影响原始对象。对于不可变对象,总是如此(根据定义)。

如果是String,则所有方法和+运算符都会返回新的String

  

不需要实现克隆

见上文。

  

在用作字段时无需进行防御性复制

从前我做过傻事。我在列表中有一组枚举:

private static final List<Status> validStatuses;

static {
  validStatuses = new ArrayList<Status>();
  validStates.add(Status.OPEN);
  validStates.add(Status.REOPENED);
  validStates.add(Status.CLOSED);
}

此列表是从方法返回的:

public static List<Status> getAllStatuses() {
  return validStates;
}

我检索了该列表但只想在界面中显示打开状态:

List<Status> statuses = Status.getAllStatuses();
statuses.remove(Status.CLOSED);

很好,它有效!等等,现在所有状态列表只显示那两个 - 即使页面刷新后!发生了什么?我修改了一个静态对象。糟糕。

我本可以在getAllStatuses的返回对象上使用防御性复制。或者,我可以首先使用类似Guava's ImmutableList的内容:

private static final List<Status> validStatuses =
    ImmutableList.of(Status.OPEN, Status.REOPENED, Status.CLOSED);

然后我做了一些愚蠢的事情:

List<Status> statuses = Status.getAllStatuses();
statuses.remove(Status.CLOSED);  // Exception!
  

始终具有“失败原子性”(Joshua Bloch使用的术语):如果不可变对象抛出异常,则它永远不会处于不合需要或不确定状态。

因为永远不能修改类,所以通过修改发出的所有状态都是完整的限定对象(因为它们不能更改,所以它们必须始终处于合格状态才有用)。异常不会发出新对象,因此您永远不会有不受欢迎或不确定的状态。

答案 1 :(得分:2)

  

它们是自动线程安全的,没有同步问题

是的,因为Java内存模型为final fields提供了保证:

final字段还允许程序员在没有同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变的,即使使用数据争用来传递线程之间的不可变对象的引用。

  

在用作字段时不需要防御性地复制如何?任何一个例子?

因为它们是不可变的,所以不能修改它们,所以可以与外部代码共享它们(你知道它们无法搞砸对象的状态)。

推论:您不需要复制/克隆不可变对象。

  

总是有“失败的原子性”

一旦正确构造,不可变对象就不会改变。因此,无论是构造失败还是异常,或者它都没有,并且您知道对象处于一致状态。

答案 2 :(得分:1)

这不是一个可以通过它的例子有用地解释的概念。不可变对象的优点是你知道他们的数据不能改变,所以你不必担心。您可以自由使用不可变对象,而不必担心传递它们的方法会改变它。

当我们执行多线程程序时比这更方便,因为基于线程更改的数据的错误不应该完成

答案 3 :(得分:0)

自动线程安全

  • 因为它们无法更改(无法变异) - 任何访问它的线程都会找到处于相同状态的对象。所以没有一种情况,比如一个线程改变对象的状态,然后第二个线程接管并改变对象的状态,然后第一个接管而没有线索,它被其他人改变了
  • 好的例子是ArrayList - 如果一个线程迭代它的&#39;元素和第二个线程删除其中的一些,然后第一个线程抛出某种并发异常。使用不可变列表可以防止

复制构造函数

  • 并不意味着它不能拥有复制构​​造函数。它是一个构造函数,您传递相同类型的对象,并创建新对象作为给定对象的副本。这只是猜测,但为什么要复制始终处于相同状态的对象?
public class A
{
    private int a;

    public A(int a)
    {
        this.a = a;
    }

    public A(A original)
    {
        this.a = original.a;
    }

}  

实施克隆

  • 同样的问题,克隆对象,总是在同一状态,通常只占用内存空间。但是你可以这样做,如果你想用不可变的
  • 创建可变对象
  • 好的例子又是集合,你可以用不可变的
  • 生成可变集合

防御性复制

  • 防御性复制意味着,当您将对象设置为字段时,您将创建相同类型的新对象,即原始副本
  • Example