福建泉州网站建设公司,提供郑州网站建设,网站排名优化化快排优化,做一个英文网站目录
1 公共静态变量逸出
2 非私有方法逸出私有变量
3 this引用逸出
4 构造函数中的可覆盖方法调用逸出 发布#xff08;publishing#xff09;一个对象的意思是#xff1a;使对象能够在当前作用域之外的代码中使用。例如#xff0c;将一个指向该对象的引用保存到其他代…目录
1 公共静态变量逸出
2 非私有方法逸出私有变量
3 this引用逸出
4 构造函数中的可覆盖方法调用逸出 发布publishing一个对象的意思是使对象能够在当前作用域之外的代码中使用。例如将一个指向该对象的引用保存到其他代码可以访问的地方或者在某一个非私有的方法中返回该引用或者将引用传递到其他类的方法中。 发布内部状态可能会破坏封装性并使程序难以维持不变性条件。例如如果在对象构造完成之前就发布该对象就会破坏线程安全性。 当某个不应该发布的对象被发布时这种情况就成为逸出escape。 简而言之发布就是把对象暴露给他人使用这就是为什么会需要用到封装逸出就是把不应该发布的对象发布了比如对象还没完成实例化就被外界使用了。
1 公共静态变量逸出 发布对象的最常见方式就是将对象的引用保存到一个公有的静态变量中任何类和线程都能看见该对象。如下代码所示initialize方法实例化一个新的HashSet实例并通过将它存储到knownSecrets引用从而发布这个实例
// 3-5 发布一个对象
public static SetSecret knownSecrets;
public void initialize() {knownSecrets new HashSetSecret();
} 当发布某个对象时可能会间接地发布其他对象。如果将一个Secret对象添加到集合knownSecrets中那么同样会发布这个对象因为任何代码都可以遍历这个集合并获得对这个新Secret对象的引用。
2 非私有方法逸出私有变量 从非私有方法中返回一个引用也能发布返回的对象。下面的代码发布了包含洲名缩写的数组而这个数组本应是私有的
// 3-6 使内部可变状态逸出(不要这样做)
public class UnsafeStates {private String[] states new String[] { AK, AL, LW };public String[] getStates() {return states;}public static void main(String[] args) {UnsafeStates us new UnsafeStates();System.out.println(Arrays.toString(us.getStates()));us.getStates()[0] NY;System.out.println(Arrays.toString(us.getStates()));}
} 这样发布states会出现问题因为任何调用者都能修改这个数组的内容。通过访问对象中的共有方法获取私有变量的值然后更改内部数据则导致变量逸出作用域。数组states已经逸出了它所在的作用域这个本该私有的数据事实上已经变成共有了。 发布一个对象时该对象的非私有域中引用的所有对象同样会被发布。更一般的一个已发布的对象中那些非私有的引用链及方法调用链中的可获得对象也都会被发布。
3 this引用逸出 最后一种发布对象或其内部状态的机制就是发布一个内部的类实例。当ThisEscape发布内部类EvnetLister时也隐含地发布了ThisEscape实例本身因为在这个内部类的实例中也包含了对ThisEscape实例的隐含引用。 1、EventListener接口
public interface EventListener {void onEvent(Object obj);
}
2、EventSource
public class EventSourceT {private final ListT eventListeners;public EventSource() {eventListeners new ArrayList();}public synchronized void registerListener(T eventListener) {this.eventListeners.add(eventListener);this.notifyAll();}public synchronized ListT retrieveListeners() throws InterruptedException {ListT dest null;if (eventListeners.size() 0) {this.wait();}dest new ArrayList(eventListeners.size());dest.addAll(eventListeners);return dest;}}
3、ThisEscape
public class ThisEscape {public final int id;public final String name;public ThisEscape(EventSource source) {id 100;// ThisEscape尝试在构造函数中注册一个事件监听器source.registerListener(new EventListener() {Overridepublic void onEvent(Object obj) {System.out.println(id: ThisEscape.this.id);System.out.println(name: ThisEscape.this.name);}});try {// 调用sleep模拟其他耗时的初始化操作Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}name ThisEscape初始化完成;}Overridepublic String toString() {return ThisEscape{ id id , name name \ };}
} ThisEscape演示了一种重要的逸出特例this引用在构造时逸出。发布的内部EventListener实例是一个封装的ThisEscape中的实例。但是这个对象只有通过构造器函数返回后才处于可预言的、稳定的状态所以从构造器函数内部发布的对象只是一个未完成构造的对象。 内部类、匿名内部类都可以访问外部类的对象的域因为内部类构造的时候会把外部类的对象this隐式的作为一个参数传递给内部类的构造方法这个工作是编译器做的它会给内部类所有的构造方法添加这个参数所以这个例子的匿名内部类在构造ThisEscape时就把ThisEscape创建的对象隐式的传给匿名内部类了。这样 source就持有ThisEscape的内部类EvenListener而Evenlistener可能会带出ThisEscape中的保护数据引用如果此时ThisEscape还未初始化完成Evenlistener可能会访问到ThisEscape中未完成初始化的数据因为this引用提前被EventListener实例对象拿到这就是this引用的逸出。
public class ThisEscapeTest {public static void main(String[] args) throws InterruptedException {EventSourceEventListener source new EventSource();new Thread(() - {try {ListEventListener listeners source.retrieveListeners();for(EventListener listener : listeners) {listener.onEvent(new Object());}} catch (InterruptedException e) {throw new RuntimeException(e);}}).start();ThisEscape escape new ThisEscape(source);System.out.println(ThisEscape 构造完成结果escape);}
} 运行结果 这个测试案例中另一个线程在ThisEscape还未完成初始化时就访问ThisEscape的内部数据了。 总结这个案例造成this逸出一个是在构造函数中创建内部类(EventListener) 另一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。那么对应的解决方法就是如果要在构造函数中创建内部类那么就不能在构造函数中将其发布了应该在构造函数外发布即等构造函数执行完毕初始化工作已全部完成再发布内部类。如果需要在构造函数中注册一个事件监听器或者启动线程可以使用一个私有的构造函数和一个公共的工厂方法Factory Method从而避免不正确的构造过程。
public class SafeListener {private final EventListener listener;private SafeListener() {listener new EventListener() {public void onEvent(Event e) {doSomething(e);}};}public static SafeListener newInstance(EventSource source) {SafeListener safe new SafeListener();source.registerListener(safe.listener);return safe;}void doSomething(Event e) {}} 如上示例代码所示注册监听在构造之后执行保证onEvent()方法在SafeListener的构造之后才能被调用对象正确初始化后再调用this引用指向的对象的方法修改属性就不是逸出而是发布。 在构造函数过程中使this引用逸出的一个常见错误是在构造器中启动一个线程。当对象在其构造函数中创建一个线程时无论是显式创建还是隐式创建this引用都会被新创建的线程共享。在对象尚未完全构造之前新的线程就可以看见它。在构造函数中创建线程并没有错误但最好不要立即启动它而是通过一个start或initialize方法来启动。
4 构造函数中的可覆盖方法调用逸出 在构造函数中调用一个可覆盖的实例方法时既不是private也不是final的同样会导致this引用在构造期间逸出。
Base类
public abstract class Base {Base() {System.out.println(Base构造函数);// 在构造函数中调用可重写的方法overrideMe();}// 一个可重写的方法abstract void overrideMe();
}
子类
public class Child extends Base{final int x;Child(int x) {System.out.println(Child构造函数);this.x x;}Overridevoid overrideMe() {System.out.println(x);}public static void main(String[] args) {new Child(42);}
} 在子类初始化时会先调用父类Base的构造函数而父类的构造函数中调用了可重写的方法实际上调用的是子类中重载的方法然而此时子类尚未完成初始化造成的结果就是尚未完成初始化的父类逸出到子类中。 运行结果 这里当Base构造函数调用时overrideMeChild尚未完成初始化final int x并且该方法获取错误的值这几乎肯定会导致错误。