public interface Freezable
implements Cloneable
| android.icu.util.Freezable<T> |
| |
提供一个灵活的机制来控制访问,而不要求一个类是不可改变的。 一旦冻结,一个对象永远不能解冻,所以从这个点开始就是线程安全的。 一旦对象被冻结,它必须保证不能对其进行更改。 任何尝试修改它都必须引发一个UnsupportedOperationException异常。 这意味着当对象返回内部对象时,或者任何人有对这些内部对象的引用时,这些内部对象必须是不可变的,或者如果尝试修改它们,还必须引发异常。 当然,该对象可以返回内部对象的克隆,因为这些对象是安全的。
有时候你需要物体是“安全”的物体,所以它们不能被修改。 例子是什么时候对象需要线程安全,或编写健壮的代码或缓存。 如果你只是创建自己的对象,当然可以保证 - 但只有在你没有犯错的时候。 如果你有物品交给你,或者正在使用交给你的其他物品来创造物品,这是一个不同的故事。 这一切都取决于你是否想采取Blanche Dubois的方法(“取决于陌生人的善意”)还是安迪格罗夫的方法(“只有偏执狂生存”)。
例如,假设我们有一个简单的类:
public class A {
protected Collection b;
protected Collection c;
public Collection get_b() {
return b;
}
public Collection get_c() {
return c;
}
public A(Collection new_b, Collection new_c) {
b = new_b;
c = new_c;
}
}
由于这个班没有任何制定者,有人可能会认为这是不可改变的。 当然,你知道这是领先的吗? 这个班级在很多方面都是不安全的。 以下说明。
public test1(SupposedlyImmutableClass x, SafeStorage y) {
// unsafe getter
A a = x.getA();
Collection col = a.get_b();
col.add(something); // a has now been changed, and x too
// unsafe constructor
a = new A(col, col);
y.store(a);
col.add(something); // a has now been changed, and y too
}
有几种不同的安全课程。
这些都有优点和缺点。
Freezable模型通过赋予您通过调用各种方法来构建对象的能力来补充这些选择,然后当它处于最终状态时,可以使其不可变。 一旦一成不变的,对象永远不能被修改,而且完全是线程安全的:那就是,多线程可以有它的引用,没有任何同步。 如果有人需要一个对象的可变版本,他们可以使用cloneAsThawed() ,并修改副本。 这提供了一个简单,有效的机制,在替代品不够或笨拙的情况下进行安全课程。 (如果一个对象在它是不可变的之前被共享,那么每个线程都有责任去使用它(与其他对象一样)。)
根据对象的类型,这是需要做什么来实现这个接口。
这些是最简单的。 您只需使用界面来反映这一点,通过添加以下内容:
public class A implements Freezable<A> {
...
public final boolean isFrozen() {return true;}
public final A freeze() {return this;}
public final A cloneAsThawed() { return this; }
}
这些可以是最终的方法,因为不可变对象的子类必须是不可变的。 (注意: freeze正在返回链接this )
添加受保护的“标记”字段:
protected volatile boolean frozen; // WARNING: must be volatile
添加以下方法:
public final boolean isFrozen() {
return frozen;
};
public A freeze() {
frozen = true; // WARNING: must be final statement before return
return this;
}
添加 cloneAsThawed()方法如下为正常模式 clone() ,除了 frozen=false在新的克隆。
然后拿setter(也就是任何可以改变对象内部状态的方法),并添加以下内容作为第一条语句:
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify frozen object");
}
任何Freezable子类Freezable将使用其超类标记字段。 它必须覆盖freeze()和cloneAsThawed()以调用超类,但通常不会覆盖isFrozen() 。 它必须注意自己的获得者,制定者和领域。
内部缓存是对象在逻辑上未被修改但对象的内部状态改变的情况。 例如,为了修改对象缓存,const C ++函数会抛出“this”指针上的const。 这些情况通过使内部缓存无效来处理,以确保线程安全。 例如,假设UnicodeSet对最后访问的代码点有一个内部标记。 在这种情况下,该字段不是外部可见的,因此您唯一需要做的就是同步该字段以确保线程安全。
如果内部字段为frozen或不可变(如字符串或基元),则称它们是安全的 。 如果你从来没有允许内部访问这些,那么你都完成了。 例如,将UnicodeSet转换为Freezable只需完成上述步骤即可。 但是请记住,如果在getter,setter或constructor中有以下任何代码, 则允许访问不安全内部:
Collection getStuff() {
return stuff;
} // caller could keep reference & modify
void setStuff(Collection x) {
stuff = x;
} // caller could keep reference & modify
MyClass(Collection x) {
stuff = x;
} // caller could keep reference & modify
这些也在上面 背景的代码示例中进行了说明。
为了处理不安全的内部事件,最简单的方法是在freeze()函数中完成这项工作。 只要让你所有的内部区域冻结,并设置冻结的标志。 任何后续的getter / setter都可以正常工作。 这里是一个例子:
警告! 'frozen'布尔值必须是易失性的,并且必须被设置为方法中的最后一个语句。
public A freeze() {
if (!frozen) {
foo.freeze();
frozen = true;
}
return this;
}
如果该字段是Collection或Map ,那么为了使其冻结,您有两个选择。 如果您从未允许从对象外部访问集合,那么只需将其包裹以防止将来进行修改。
zone_to_country = Collections.unmodifiableMap(zone_to_country);
如果你 曾经允许访问,那么在包装它之前做一个 clone() 。
zone_to_country = Collections.unmodifiableMap(zone_to_country.clone());
如果一个集合(或任何其他对象容器)本身可以包含可变对象,那么对于一个安全的克隆来说,你需要通过递归来使整个集合不可变。 递归代码应该选择最具体的可用集合,以避免以后需要进行退出。
注意: Java中一个令人讨厌的缺陷是像
Map或Set这样的通用集合没有clone()操作。 当你不知道集合的类型时,最简单的过程就是创建一个新集合:zone_to_country = Collections.unmodifiableMap(new HashMap(zone_to_country));
Public methods |
|
|---|---|
abstract T |
cloneAsThawed() 提供克隆操作。 |
abstract T |
freeze() 冻结对象。 |
abstract boolean |
isFrozen() 确定对象是否被冻结。 |