6

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

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

但是我不明白不可变对象的以下用途。

他们是

  • 是自动线程安全的并且没有同步问题。How ? Proof ?
  • 不需要复制构造函数。How ? Any example ?
  • 不需要克隆的实现How ? Any example ?
  • 用作场时不需要防守复制How ? Any example ?
  • always have "failure atomicity" (a term used by Joshua Bloch):如果不可变对象抛出异常,它永远不会处于不希望或不确定的状态。How ? Any example ?

有人可以用支持它的例子详细解释这些要点吗?

谢谢。

4

4 回答 4

10

..自动线程安全并且没有同步问题

当两个不同的线程修改同一个对象的状态时,就会出现并发问题。不可变对象不能被修改,所以没有问题。

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

不需要复制构造函数

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

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

在 的情况下String,所有方法和+运算符都返回 new 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 的 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 使用的术语):如果不可变对象抛出异常,它永远不会处于不希望或不确定的状态。

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

于 2013-07-18T08:26:31.083 回答
2

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

是的,因为 Java 内存模型为最终字段提供了保证:

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

用作字段时不需要复制防守如何?有什么例子吗?

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

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

总是有“故障原子性”

不可变对象一旦正确构造就不会改变。所以要么构造失败并且你得到一个异常,要么它没有并且你知道对象处于一致状态。

于 2013-07-18T08:27:20.837 回答
1

这不是一个可以通过示例进行有用解释的概念。不可变对象的优点是您知道它们的数据无法更改,因此您不必担心这一点。您可以自由地使用不可变对象,而不必担心传递它们的方法会改变它。

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

于 2013-07-18T08:42:41.563 回答
0

自动线程安全

  • 因为它们不能被改变(不能变异)——任何访问它的线程都会找到处于相同状态的对象。所以不会出现像一个线程改变对象的状态,然后第二个线程接管并改变对象的状态,然后第一个线程再次接管而没有任何线索的情况,它被其他人改变了
  • 很好的例子是 ArrayList - 如果一个线程遍历它的元素并且第二个线程删除了其中的一些,那么第一个线程会抛出某种并发异常。使用不可变列表可以防止这种情况

复制构造函数

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

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

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

}  

克隆的实现

  • 同样的问题,克隆对象,始终处于相同状态通常只占用内存空间。但是你可以做到,如果你想从不可变对象中创建可变对象
  • 很好的例子是集合,你可以从不可变中生成可变集合

防御性复制

  • 防御性副本意味着,当您将对象设置为字段时,您会创建相同类型的新对象,该对象是原始对象的副本
  • 例子
于 2013-07-18T08:47:20.157 回答