從前有一個人,他認為世上只有一只不尋常的狗,所以他寫出了如下的類,將它作為一個單件(singleton)[Gamma95]:
public class Dog extends Exception {
public static final Dog INSTANCE = new Dog();
private Dog() {}
public String toString(){
return "Woof";
}
}
結(jié)果證明這個人的做法是錯誤的。你能夠在這個類的外部不使用反射來創(chuàng)建出第2個Dog實例嗎?
這個類可能看起來像一個單件,但它并不是。問題在于,Dog擴展了Exception,而Exception實現(xiàn)了java.io.Serializable。這就意味著Dog是可序列化的(serializable),并且解序列(deserialization)會創(chuàng)建一個隱藏的構(gòu)造器。正如下面的這段程序所演示的,如果你序列化了Dog.INSTANCE,然后對得到的字節(jié)序列(byte sequence)進行解序列,最后你就會得到另外一個Dog。該程序打印的是false,表示新的Dog實例和原來的那個實例是不同的,并且它還打印了Woof,說明新的Dog實例也具有相應(yīng)的功能:
import java.io.*;
public class CopyDog{ // Not to be confused with copycat
public static void main(String[] args){
Dog newDog = (Dog) deepCopy(Dog.INSTANCE);
System.out.println(newDog == Dog.INSTANCE);
System.out.println(newDog);
}
// This method is very slow and generally a bad idea!
static public Object deepCopy(Object obj){
try{
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException(e);
}
}
}
要訂正這個問題,可在Dog中添加一個readResolve方法,它可以將那個隱藏的構(gòu)造器轉(zhuǎn)變?yōu)橐粋€隱藏的靜態(tài)工廠(static factory),以返回原來那個的Dog [EJ Items 2,57]。在Dog中添加了這個方法之后,CopyDog將打印true而不是false,表示那個“復(fù)本”實際上就是原來的那個實例:
private Object readResolve(){
// Accept no substitues!
return INSTANCE;
}
這個謎題的主要教訓(xùn)就是一個實現(xiàn)了Serializable的單件類,必須有一個readResolve方法,用以返回它的的實例。一個次要的教訓(xùn)就是,有可能由于對一個實現(xiàn)了Serializable的類進行了擴展,或者由于實現(xiàn)了一個擴展自Serializable的接口,使得我們在無意中實現(xiàn)了Serializable。給平臺設(shè)計者的教訓(xùn)是,隱藏的構(gòu)造器,例如序列化中產(chǎn)生的那個,會讓讀者對程序行為的產(chǎn)生錯覺。
public class Dog extends Exception {
public static final Dog INSTANCE = new Dog();
private Dog() {}
public String toString(){
return "Woof";
}
}
結(jié)果證明這個人的做法是錯誤的。你能夠在這個類的外部不使用反射來創(chuàng)建出第2個Dog實例嗎?
這個類可能看起來像一個單件,但它并不是。問題在于,Dog擴展了Exception,而Exception實現(xiàn)了java.io.Serializable。這就意味著Dog是可序列化的(serializable),并且解序列(deserialization)會創(chuàng)建一個隱藏的構(gòu)造器。正如下面的這段程序所演示的,如果你序列化了Dog.INSTANCE,然后對得到的字節(jié)序列(byte sequence)進行解序列,最后你就會得到另外一個Dog。該程序打印的是false,表示新的Dog實例和原來的那個實例是不同的,并且它還打印了Woof,說明新的Dog實例也具有相應(yīng)的功能:
import java.io.*;
public class CopyDog{ // Not to be confused with copycat
public static void main(String[] args){
Dog newDog = (Dog) deepCopy(Dog.INSTANCE);
System.out.println(newDog == Dog.INSTANCE);
System.out.println(newDog);
}
// This method is very slow and generally a bad idea!
static public Object deepCopy(Object obj){
try{
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException(e);
}
}
}
要訂正這個問題,可在Dog中添加一個readResolve方法,它可以將那個隱藏的構(gòu)造器轉(zhuǎn)變?yōu)橐粋€隱藏的靜態(tài)工廠(static factory),以返回原來那個的Dog [EJ Items 2,57]。在Dog中添加了這個方法之后,CopyDog將打印true而不是false,表示那個“復(fù)本”實際上就是原來的那個實例:
private Object readResolve(){
// Accept no substitues!
return INSTANCE;
}
這個謎題的主要教訓(xùn)就是一個實現(xiàn)了Serializable的單件類,必須有一個readResolve方法,用以返回它的的實例。一個次要的教訓(xùn)就是,有可能由于對一個實現(xiàn)了Serializable的類進行了擴展,或者由于實現(xiàn)了一個擴展自Serializable的接口,使得我們在無意中實現(xiàn)了Serializable。給平臺設(shè)計者的教訓(xùn)是,隱藏的構(gòu)造器,例如序列化中產(chǎn)生的那個,會讓讀者對程序行為的產(chǎn)生錯覺。