Java不可变对象



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

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

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

它们是

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

有人能详细解释一下中的每一点吗并举例支持吗?

谢谢。

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

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

示例:String。两个线程可以毫无顾虑地传递相同的String,因为两者都不能以任何方式对其进行变异。

不需要复制构造函数

因为复制是改变它的唯一方法。一种针对不可变对象的通用设计模式用于每次"修改"操作,以创建一个副本,然后对新对象执行操作。

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

String的情况下,所有方法和+运算符都返回新的Strings。

不需要实现克隆

请参见上文。

用作字段时不需要进行防御复制

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

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使用的一个术语):如果一个不可变对象抛出异常,它就永远不会处于不希望的或不确定的状态

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

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

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

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

当用作字段时,不需要进行防御复制如何?有什么例子吗?

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

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

始终具有"故障原子性"

一个不可变的对象一旦被正确构造就不会改变。因此,要么构建失败,得到一个异常,要么没有,知道对象处于一致状态。

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

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

中完成

自动线程安全

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

复制构造函数

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

克隆的实现

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

防御复制

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

最新更新