这是来自《Java并发编程实践》的一句话。
共享的只读对象包括不可变对象和有效不可变对象。
不可变对象和有效不可变对象有什么区别?
这是来自《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是可变的,但是如果你的类总是返回ArrayList的副本,并且你的类中其他所有内容都是不可变的,那么你的类就成为了实际上的不可变类:一个类的实例状态是不可变的。
博客文章提供了一个Effectively Immutable Class的示例:
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等方式),因此它们可以安全地在多个线程之间共享,因为从共享对象读取不会对多个线程产生伤害。
有效的不可变对象可能在被多个线程共享之前更改其状态,但是在它们被“发布”(即给出多个引用以供几个线程使用)后,它们会保护自己免受修改。
不变对象阻止您使用有用的软件工程实践,例如延迟初始化,因为为了懒惰地初始化属性或字段,它们必须是可变的,从而违反了它们无忧并发共享属性。有效的不可变对象通过仔细知道何时可以安全地修改其内部状态以及何时禁止修改来放松这些约束,从而实现了最佳的两个方面的方法。