1. 概念
“发布(Publish)”一个对象:使对象能够在当前作用域之外的代码中使用。
例如:将一个指向该对象的引用保到其他代码可以访问的地方,或者在某个非私有的方法中返回该引用,或者将引用传递到其他类的方法中。
发布内部状态可能会破坏封装性,并使得程序难以维持不变性条件。
“逸出(Escape)”:当某个不应该被发布的对象被发布时,这种情况称为逸出。
2. 对象发布的方式
发布对象的最简单方法是将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看见该对象。如下代码所示,在initialize方法中实例化一个新的HashSet对象,并将对象的引用保存到knownSecrets中以发布该对象。
public static Set<Secret> knownSecrets;
public void initialize(){
knownSecrets = new HashSet<>();
}
如果从非私有方法中返回一个引用,那么同样会发布返回的对象,如下代码所示,UnsafeStates发布了本应为私有的状态数组。
class UnsafeStates {
private String[] states = new String[]{"HSH", "KK" ... }
public String[] getStates() { return states; }
}
还有一种发布对象或其内部状态的机制就是发布一个内部类的实例。如第三节中所示。
当发布一个对象时,在该对象中的非私有域中引用的所有对象同样会被发布。如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他的对象,那么这些对象也都会发布。
3. 逸出
当发布了一个对象后,无法知道哪些代码会执行,也不知道在外部方法中究竟会发布这个对象,还是会保留对象的引用并在随后又另一个线程使用。无论其他的线程会对已发布的引用执行何种操作,其实都不重要,因为误用该引用的风险始终存在。当某个对象逸出后,你必须假设有某个类或线程可能会误用该对象。这正是需要使用封装的最主要原因:封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。
this引用逸出
在下面的代码中,当ThisEscape发布EventListener时,也隐含地发布了ThisEscape实例本身,因为在这个内部类的实例中包含了对ThisEscape实例的隐含引用。
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
@Override
public void onEvent(Event event) {
doSomething(event);
}
});
}
public static void main(String[] args) {
new ThisEscape(new EventSource());
}
private void doSomething(Event event){
System.out.println("doSomething");
}
}
public class EventSource {
public void registerListener(EventListener eventListener){
eventListener.onEvent(new Event());
}
}
public interface EventListener {
void onEvent(Event event);
}
public class Event {
}
不要在构造过程中使this引用逸出。
在ThisEscape中给出了逸出的一个特殊示例,即this引用在构造函数中逸出。当内部的EventListener实例发布时,在外部封装的ThisEscape实例也逸出了。当且仅当对象的构造函数返回时,对象才处于可预测的和一致的状态。因此,当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象。即使发布对象的语句位于构造函数的最后一行也是如此。如果this引用在构造过程中逸出,那么这种对象就被认为是不正确构造。