Java不可变对象

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

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

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

他们是

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

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

谢谢。

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

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

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

不需要复制构造函数

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

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

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

不需要克隆的实现

往上看。

当用作场时,不需要防御性地复制

曾几何时我做了些傻事。 我在List中有一组枚举:

 private static final List validStatuses; static { validStatuses = new ArrayList(); validStates.add(Status.OPEN); validStates.add(Status.REOPENED); validStates.add(Status.CLOSED); } 

此列表是从方法返回的:

 public static List getAllStatuses() { return validStates; } 

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

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

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

我本可以在getAllStatuses的返回对象上使用防御性复制。 或者,我可以首先使用像Guava的ImmutableList这样的东西:

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

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

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

总是有“失败primefaces性”(Joshua Bloch使用的术语):如果一个不可变对象抛出exception,它就永远不会处于不受欢迎或不确定的状态。

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

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

是的,因为Java Memory Model为最终字段提供了保证:

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

用作场时不需要防守复制如何? 任何例子?

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

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

总是有“失败primefaces性”

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

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

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

自动线程安全

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

复制构造函数

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

克隆的实现

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

防御性复制

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