`
vking_wang
  • 浏览: 9986 次
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

Effective Java:Ch2_创建销毁对象:Item3_通过私有构造函数或枚举类型来强化Singleton属性

 
阅读更多

所谓Singleton,是指仅能被实例化一次的类。Singleton通常代表本质上唯一的系统组件,例如窗口管理器或文件系统。让一个类成为Singleton就无法为Singleton替换模拟实现,除非它实现一个作为其类型的接口,所以会让其客户端难于测试。

JDK1.5之前,有两种方法可以实现Singleton,二者均基于让构造函数私有化,并导出一个公共静态成员来提供对唯一实例的访问。在方法一中,该公共静态成员是一个final域:

//Singleton with public final field
public class Elvis{
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){...}

    public void leaveTheBuilding(){...}
}
这里私有的构造函数仅被调用一次,用来初始化公共静态final域Elvis.INSTANCE。由于未提供public或protected的构造函数,所以保证了Elvis的全局唯一:一旦Elvis类被实例化,就只会存在唯一的一个Elvis实例,不多也不少。客户端的任何行为都改变不了这一点,不过要提醒一点:享有特权的客户端可以借助AccessibleObject.setAccessible()方法,通过反射(Item53)调用private构造函数。如果你需要防止这种攻击,则修改构造函数,当它被调用来创建第二个实例时抛出一个异常。


在实现Singleton的方法二中,该公共静态成员是一个静态工厂方法:

//Singleton with static factory
public class Elvis{
    private static final Elvis INSTANCE = new Elvis();
    private Elvis(){...}
    public static Elvis getInstance(){
        return INSTANCE;
    }

    public void leaveTheBuilding(){...}
}
对Elvis.getInstance()的所有调用都返回相同的对象引用,不会创建任何其他的Elvis实例。(上文的提醒也要关注)。


public域方法(方法一)的主要优点是,类声明清楚地表明该类是Singleton:公共的静态域是final的,所以该域总是包含相同的对象引用。public域方法的性能优势已不存在了:现代的java虚拟机实现差不多都将其调用内联到静态工厂方法。

工厂方法(方法二)的一个优点是,你可以灵活地决定是否将类设为Singleton,而不用改变其API。例如,返回唯一实例的工厂方法,可以很容易地改为为每个调用它的线程返回一个唯一实例。第二个优点和Item27所讨论的泛型有关。通常这些优势都不相关,而且静态域方法更简单。


为了将使用上述方法的Singleton类变为可序列化,单单增加implements Serializable是不够的。为了维持Singleton的保证,你还需要将所有实例变量声明为transient,并提供readResolve方法*(Item77)。否则,每次实例反序列化时,都会创建一个新的实例。在上例中就会导致“假冒的Elvis”,为了防止这种情况,需要在Elvis类中增加readResolve方法:

//readResolve method to preserve singleton property
private Object readResolve(){
    //return the one true Elvis and let the garbage collector take care of the Elvis impersonator
    return INSTANCE;
}


JDK1.5之后,还有第三种方法来实现Singleton,只要简单地编写一个只包含一个元素的枚举类型:
//Enum singletion - the preferred approach
public enum Elvis{
    INSTANCE;

    public void leaveTheBuilding(){...}
}
该方法与公共域方法类似,不过它更加简洁,无偿提供序列化机制,并且绝对保证不会被多次实例化,即使是在面对复杂的序列化或反射攻击的时候也这样。虽然这种方法尚未被广泛采用,但单元素的枚举类型是实现Singleton的最佳方式。



#######################################

关于readResolve():

JDK API描述:

将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:

 ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

在从流中读取类的一个实例时需要指定替代的类,应使用的准确签名来实现此特殊方法。

 ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

此 writeReplace、readResolve 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。


也就是说,

writeReplace()方法返回的对象,就是要被序列化的对象,我们有机会在序列化前把这个对象给换成我们确定好的那个(貌似没什么用处);

readResolve()方法就是在反序列化完成得到对象前,把这个对象给换成我们确定好的那个。

为了防止有人恶意通过序列化的机制破坏定义好的单例,就需要自己实现readResolve()方法,把单例定义的唯一实现在这个方法中返回。





分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics