这是Java并发实践中的一句话
共享只读对象包括不可变对象和有效不可变对象。
不可变对象和有效不可变对象之间有什么区别?
不可扩展且其字段全部final
且自身不可变的类的实例是不可变的。
一个类的实例,其字段由于其方法的细节而无法改变,实际上是不可变的。例如:
final class C {
final boolean canChange;
private int x;
C(boolean canChange) { this.canChange = canChange; }
public void setX(int newX) {
if (canChange) {
this.x = newX;
} else {
throw new IllegalStateException();
}
}
}
的某些实例C
实际上是不可变的,而有些则不是。
另一个例子是零长度数组。它们实际上是不可变的,即使它们的包含类不可证明是不可变的,因为它们中没有可以更改的元素。
Joe-E 使用验证器来证明某些类只允许不可变实例。任何用Immutable
标记接口标记的东西都会被检查,并且某些类String
(实际上是不可变的,因为它char[]
不会转义)被认为是不可变的。
由 Joe-E 库定义的 Immutable 接口由该语言特殊处理:Joe-E 验证器检查实现此接口的每个对象是否(深度)不可变,如果不能自动生成,则会引发编译时错误已验证。
这是我通过谷歌搜索和找到这篇文章的理解。Effectively Immutable Object 是一个包含可以改变的字段的对象,但它不会让任何东西改变这些字段,因为它永远不会给你提供对它们的引用。例如,假设您创建了一个类,其中包含一个ArrayList
。 ArrayList
s 是可变的,但是如果您的类总是返回 ArrayList 的副本,并且您的类中的所有其他内容都是不可变的,那么您的类实际上就变成了不可变的:没有办法改变您的类实例的状态。
博客文章将此作为有效不可变类的示例:
import java.awt.*;
public class Line {
private final Point start;
private final Point end;
public Line(final Point start, final Point end) {
this.start = new Point(start);
this.end = new Point(end);
}
public void draw() {
//...
}
public Point getStart() {
return new Point(start);
}
public Point getEnd() {
return new Point(end);
}
}
Point
对象是可变的,但没关系,因为这个类没有给任何人直接引用它的Point 实例。相反,它返回一个具有相同值的新实例。这样,没有人可以改变Line
类的状态。这使得Line
类实际上是不可变的。
那么这与真正不可变的类有什么不同呢?真正不可变的类具有同样不可变的字段。让我们想象一下Line
它是真正不可变的。为此,我们还必须想象它Point
是不可变的。做出这些假设,该getStart()
方法可以写成这样:
public Point getStart() {
return start;
}
看看这个答案:
有效不可变和不可变有效不可变和不可变之间的区别在于,在第一种情况下,您仍然需要以安全的方式发布对象。对于不需要的真正不可变的对象。所以真正不可变的对象是首选,因为它们更容易发布,我上面提到的原因说明了你可能更喜欢非同步发布的原因。
不可变对象完全封装了它们的内部状态,并且它们不允许在构造后修改该状态(可能使用 final 等),因此它们可以安全地在多个线程之间共享,因为从共享对象中读取对多个线程无害。
有效的不可变对象可能会在多个线程之间共享之前改变它们的状态,但是在它们被“发布”之后(即多个引用被赋予多个线程),它们会保护自己不被修改。
不可变对象阻止您使用有用的软件工程实践,例如延迟初始化,因为为了延迟初始化属性或字段,它们必须是可变的,违反了它们的无忧并发共享属性。有效的不可变对象通过仔细了解何时可以安全地修改其内部状态以及何时被禁止来放松这些约束,从而获得两全其美的方法。