一個比較誘人的想法是用序列化技術保存程序的一些狀態信息,從而將程序方便地恢復到以前的狀態。但在具體實現以前,有些問題是必須解決的。如果兩個對象都有指向第三個對象的句柄,該如何對這兩個對象序列化呢?如果從兩個對象序列化後的狀態恢復它們,第三個對象的句柄只會出現在一個對象身上嗎?如果將這兩個對象序列化成獨立的文件,然後在代碼的不同部分重新裝配它們,又會得到什麼結果呢?
下面這個例子對上述問題進行了很好的說明:
//: MyWorld.java
import java.io.*;
import java.util.*;
class House implements Serializable {}
class Animal implements Serializable {
String name;
House preferredHouse;
Animal(String nm, House h) {
name = nm;
preferredHouse = h;
}
public String toString() {
return name + "[" + super.toString() +
"], " + preferredHouse + "\n";
}
}
public class MyWorld {
public static void main(String[] args) {
House house = new House();
Vector animals = new Vector();
animals.addElement(
new Animal("Bosco the dog", house));
animals.addElement(
new Animal("Ralph the hamster", house));
animals.addElement(
new Animal("Fronk the cat", house));
System.out.println("animals: " + animals);
try {
ByteArrayOutputStream buf1 =
new ByteArrayOutputStream();
ObjectOutputStream o1 =
new ObjectOutputStream(buf1);
o1.writeObject(animals);
o1.writeObject(animals); // Write a 2nd set
// Write to a different stream:
ByteArrayOutputStream buf2 =
new ByteArrayOutputStream();
ObjectOutputStream o2 =
new ObjectOutputStream(buf2);
o2.writeObject(animals);
// Now get them back:
ObjectInputStream in1 =
new ObjectInputStream(
new ByteArrayInputStream(
buf1.toByteArray()));
ObjectInputStream in2 =
new ObjectInputStream(
new ByteArrayInputStream(
buf2.toByteArray()));
Vector animals1 = (Vector)in1.readObject();
Vector animals2 = (Vector)in1.readObject();
Vector animals3 = (Vector)in2.readObject();
System.out.println("animals1: " + animals1);
System.out.println("animals2: " + animals2);
System.out.println("animals3: " + animals3);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
這裡一件有趣的事情是也許是能針對一個字節數組應用對象的序列化,從而實現對任何Serializable(可序列化)對象的一個“全面復制”(全面復制意味著復制的是整個對象網,而不僅是基本對象和它的句柄)。復制問題將在第12章進行全面講述。
Animal對象包含了類型為House的字段。在main()中,會創建這些Animal的一個Vector,並對其序列化兩次,分別送入兩個不同的數據流內。這些數據重新裝配並打印出來後,可看到下面這樣的結果(對象在每次運行時都會處在不同的內存位置,所以每次運行的結果有區別):
animals: [Bosco the dog[Animal@1cc76c], House@1cc769 , Ralph the hamster[Animal@1cc76d], House@1cc769 , Fronk the cat[Animal@1cc76e], House@1cc769 ] animals1: [Bosco the dog[Animal@1cca0c], House@1cca16 , Ralph the hamster[Animal@1cca17], House@1cca16 , Fronk the cat[Animal@1cca1b], House@1cca16 ] animals2: [Bosco the dog[Animal@1cca0c], House@1cca16 , Ralph the hamster[Animal@1cca17], House@1cca16 , Fronk the cat[Animal@1cca1b], House@1cca16 ] animals3: [Bosco the dog[Animal@1cca52], House@1cca5c , Ralph the hamster[Animal@1cca5d], House@1cca5c , Fronk the cat[Animal@1cca61], House@1cca5c ]
當然,我們希望裝配好的對象有與原來不同的地址。但注意在animals1和animals2中出現了相同的地址,其中包括共享的、對House對象的引用。在另一方面,當animals3恢復以後,系統沒有辦法知道另一個流內的對象是第一個流內對象的化身,所以會產生一個完全不同的對象網。
只要將所有東西都序列化到單獨一個數據流裡,就能恢復獲得與以前寫入時完全一樣的對象網,不會不慎造成對象的重復。當然,在寫第一個和最後一個對象的時間之間,可改變對象的狀態,但那必須由我們明確采取操作——序列化時,對象會采用它們當時的任何狀態(包括它們與其他對象的連接關系)寫入。
若想保存系統狀態,最安全的做法是當作一種“微觀”操作序列化。如果序列化了某些東西,再去做其他一些工作,再來序列化更多的東西,以此類推,那麼最終將無法安全地保存系統狀態。相反,應將構成系統狀態的所有對象都置入單個集合內,並在一次操作裡完成那個集合的寫入。這樣一來,同樣只需一次方法調用,即可成功恢復之。
下面這個例子是一套假想的計算機輔助設計(CAD)系統,對這一方法進行了很好的演示。此外,它還為我們引入了static字段的問題——如留意聯機文檔,就會發現Class是“Serializable”(可序列化)的,所以只需簡單地序列化Class對象,就能實現static字段的保存。這無論如何都是一種明智的做法。
//: CADState.java
// Saving and restoring the state of a
// pretend CAD system.
import java.io.*;
import java.util.*;
abstract class Shape implements Serializable {
public static final int
RED = 1, BLUE = 2, GREEN = 3;
private int xPos, yPos, dimension;
private static Random r = new Random();
private static int counter = 0;
abstract public void setColor(int newColor);
abstract public int getColor();
public Shape(int xVal, int yVal, int dim) {
xPos = xVal;
yPos = yVal;
dimension = dim;
}
public String toString() {
return getClass().toString() +
" color[" + getColor() +
"] xPos[" + xPos +
"] yPos[" + yPos +
"] dim[" + dimension + "]\n";
}
public static Shape randomFactory() {
int xVal = r.nextInt() % 100;
int yVal = r.nextInt() % 100;
int dim = r.nextInt() % 100;
switch(counter++ % 3) {
default:
case 0: return new Circle(xVal, yVal, dim);
case 1: return new Square(xVal, yVal, dim);
case 2: return new Line(xVal, yVal, dim);
}
}
}
class Circle extends Shape {
private static int color = RED;
public Circle(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) {
color = newColor;
}
public int getColor() {
return color;
}
}
class Square extends Shape {
private static int color;
public Square(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
color = RED;
}
public void setColor(int newColor) {
color = newColor;
}
public int getColor() {
return color;
}
}
class Line extends Shape {
private static int color = RED;
public static void
serializeStaticState(ObjectOutputStream os)
throws IOException {
os.writeInt(color);
}
public static void
deserializeStaticState(ObjectInputStream os)
throws IOException {
color = os.readInt();
}
public Line(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) {
color = newColor;
}
public int getColor() {
return color;
}
}
public class CADState {
public static void main(String[] args)
throws Exception {
Vector shapeTypes, shapes;
if(args.length == 0) {
shapeTypes = new Vector();
shapes = new Vector();
// Add handles to the class objects:
shapeTypes.addElement(Circle.class);
shapeTypes.addElement(Square.class);
shapeTypes.addElement(Line.class);
// Make some shapes:
for(int i = 0; i < 10; i++)
shapes.addElement(Shape.randomFactory());
// Set all the static colors to GREEN:
for(int i = 0; i < 10; i++)
((Shape)shapes.elementAt(i))
.setColor(Shape.GREEN);
// Save the state vector:
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("CADState.out"));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
} else { // There's a command-line argument
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream(args[0]));
// Read in the same order they were written:
shapeTypes = (Vector)in.readObject();
Line.deserializeStaticState(in);
shapes = (Vector)in.readObject();
}
// Display the shapes:
System.out.println(shapes);
}
} ///:~
Shape(幾何形狀)類“實現了可序列化”(implements Serializable),所以從Shape繼承的任何東西也都會自動“可序列化”。每個Shape都包含了數據,而且每個衍生的Shape類都包含了一個特殊的static字段,用於決定所有那些類型的Shape的顏色(如將一個static字段置入基礎類,結果只會產生一個字段,因為static字段未在衍生類中復制)。可對基礎類中的方法進行覆蓋處理,以便為不同的類型設置顏色(static方法不會動態綁定,所以這些都是普通的方法)。每次調用randomFactory()方法時,它都會創建一個不同的Shape(Shape值采用隨機值)。
Circle(圓)和Square(矩形)屬於對Shape的直接擴展;唯一的差別是Circle在定義時會初始化顏色,而Square在構建器中初始化。Line(直線)的問題將留到以後討論。
在main()中,一個Vector用於容納Class對象,而另一個用於容納形狀。若不提供相應的命令行參數,就會創建shapeTypes Vector,並添加Class對象。然後創建shapes Vector,並添加Shape對象。接下來,所有static color值都會設成GREEN,而且所有東西都會序列化到文件CADState.out。
若提供了一個命令行參數(假設CADState.out),便會打開那個文件,並用它恢復程序的狀態。無論在哪種情況下,結果產生的Shape的Vector都會打印出來。下面列出它某一次運行的結果:
>java CADState [class Circle color[3] xPos[-51] yPos[-99] dim[38] , class Square color[3] xPos[2] yPos[61] dim[-46] , class Line color[3] xPos[51] yPos[73] dim[64] , class Circle color[3] xPos[-70] yPos[1] dim[16] , class Square color[3] xPos[3] yPos[94] dim[-36] , class Line color[3] xPos[-84] yPos[-21] dim[-35] , class Circle color[3] xPos[-75] yPos[-43] dim[22] , class Square color[3] xPos[81] yPos[30] dim[-45] , class Line color[3] xPos[-29] yPos[92] dim[17] , class Circle color[3] xPos[17] yPos[90] dim[-76] ] >java CADState CADState.out [class Circle color[1] xPos[-51] yPos[-99] dim[38] , class Square color[0] xPos[2] yPos[61] dim[-46] , class Line color[3] xPos[51] yPos[73] dim[64] , class Circle color[1] xPos[-70] yPos[1] dim[16] , class Square color[0] xPos[3] yPos[94] dim[-36] , class Line color[3] xPos[-84] yPos[-21] dim[-35] , class Circle color[1] xPos[-75] yPos[-43] dim[22] , class Square color[0] xPos[81] yPos[30] dim[-45] , class Line color[3] xPos[-29] yPos[92] dim[17] , class Circle color[1] xPos[17] yPos[90] dim[-76] ]
從中可以看出,xPos,yPos以及dim的值都已成功保存和恢復出來。但在獲取static信息時卻出現了問題。所有“3”都已進入,但沒有正常地出來。Circle有一個1值(定義為RED),而Square有一個0值(記住,它們是在構建器裡初始化的)。看上去似乎static根本沒有得到初始化!實情正是如此——盡管類Class是“可以序列化的”,但卻不能按我們希望的工作。所以假如想序列化static值,必須親自動手。
這正是Line中的serializeStaticState()和deserializeStaticState()兩個static方法的用途。可以看到,這兩個方法都是作為存儲和恢復進程的一部分明確調用的(注意寫入序列化文件和從中讀回的順序不能改變)。所以為了使CADState.java正確運行起來,必須采用下述三種方法之一:
(1) 為幾何形狀添加一個serializeStaticState()和deserializeStaticState()。
(2) 刪除Vector shapeTypes以及與之有關的所有代碼
(3) 在幾何形狀內添加對新序列化和撤消序列化靜態方法的調用
要注意的另一個問題是安全,因為序列化處理也會將private數據保存下來。若有需要保密的字段,應將其標記成transient。但在這之後,必須設計一種安全的信息保存方法。這樣一來,一旦需要恢復,就可以重設那些private變量。