### 1. 對象序列化
當你創建對象時,只要你需要,它會一直存在,但是程序終止時,無論何時它都不會繼續存在。盡管這樣做是非常有意義的,但是在某些情況下,如果程序不運行時扔能存在并且保存其信息,那將對我們非常有用。這樣,在下次程序運行時,該對象將被重建并且擁有的信息與程序上次運行時它所擁有的信息相同。當然,我們也可以通過將信息寫入文件或者數據庫,但是如果能將一個對象聲明為是"持久性"的,并為我們處理掉所有的細節,這將會顯得十分方便。
Java的序列化是將那些實現了Serializable接口的對象轉換為一個字節序列,并能夠在以后需要的時候將這個字節序列完全恢復為原來的對象。我們可以在windows系統機器上創建一個對象,將其序列化,通過網絡將它發送給一臺運行Unix系統的計算機,然后在那里準確的重新組裝恢復為原來的對象,所以說不必擔心數據在不同機器上的表示會不同,也不必關心字節的順序或者其他任何細節。這意味著序列化機制能自動彌補不同操作系統之間的差異。
對象序列化是為了支持兩種特性。一是Java的遠程方法調用(RMI),它使存活于其他計算機上的對象使用起來就像存活于本機上一樣。當向遠程對象發送消息時,需要通過對象序列化來傳輸參數和返回值。二是Java Beans,使用一個bean時,一般情況下是在設計階段對它的狀態信息進行配置。這種狀態信息必須保存下來,并在程序啟動時進行后期恢復(這種具體工作就是由對象序列化完成的)。
要序列化一個對象,首先要創建一個OutputStream對象,然后將其封裝在一個ObjectOutputStream對象內。這時,只需調用writeObject()即可將對象序列化,并將其發送給 OutputStream(對象序列化是基于字節的,因要使用InputStream和OutputStream繼承層次結構)。要反向進行該過程(將一個序列化還原為一個對象),需要將一個InputStream封裝在ObjectInputStream內,然后調用readObject()。和往常一樣,我們最后獲得的是一個引用,它指向一個向上轉型的Object,所以必須向下轉型才能直接設置它們。
~~~
package com.qunar.bean;
import java.io.Serializable;
/**
* 學生實體類
* @author sjf0115
*
*/
public class Student implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
// 姓名
private String name;
// 學號
private String ID;
// 年齡
private int age;
// 學校
private String school;
/**
* @param name
* @param iD
* @param age
* @param school
*/
public Student(String name, String id, int age, String school) {
super();
this.name = name;
ID = id;
this.age = age;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getID() {
return ID;
}
public void setID(String iD) {
ID = iD;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "姓名:" + name + " 學號:" + ID + " 年齡:" + age + " 學校:" + school;
}
}
~~~
~~~
package com.qunar.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.qunar.bean.Student;
public class SeriaCode {
public static void main(String[] args) {
// 對象序列化數據保存位置
String path = "D:\\seria.dat";
try {
// 創建FileOutputStream對象
FileOutputStream fileOutputStream = new FileOutputStream(path);
// 創建ObjectOutputStream對象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 序列化對象
Student student = new Student("xiaosi","130346",25,"西安電子科技大學");
// 進行對象序列化 Student對象要實現序列化接口
objectOutputStream.writeObject(student);
objectOutputStream.flush();
objectOutputStream.close();
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream(path);
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
Student stu = (Student)objectInputStream.readObject();
objectInputStream.close();
System.out.println("Stu->"+stu);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
### 2.尋找類
將一個對象從它的序列化狀態中恢復出來,有哪些工作是必須的?舉例來說,假如我們將一個對象序列化,并通過網絡將其作為文件傳送給另一臺計算機,那么另一臺計算機上的程序可以只利用該文件內容來還原這個對象嗎?
~~~
package com.qunar.io.serial;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class SerialCode2 {
public static void main(String[] args) {
try {
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream("seria.dat");
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
Object object = objectInputStream.readObject();
objectInputStream.close();
System.out.println(object.getClass());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
假設在運行上面程序之前,將Student類刪除,在運行,會得到:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:rgb(0,0,128); font-family:微軟雅黑"><u>java.lang.ClassNotFoundException<span style="text-decoration:none; color:rgb(255,0,0)">:?com.qunar.io.Student</span></u></span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.net.URLClassLoader$1.run(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.net.URLClassLoader$1.run(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.security.AccessController.doPrivileged(<span style="color:rgb(0,0,128)"><u>Native?Method</u></span>)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.net.URLClassLoader.findClass(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.lang.ClassLoader.loadClass(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?sun.misc.Launcher$AppClassLoader.loadClass(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.lang.ClassLoader.loadClass(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.lang.Class.forName0(<span style="color:rgb(0,0,128)"><u>Native?Method</u></span>)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.lang.Class.forName(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.resolveClass(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readNonProxyDesc(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readClassDesc(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readOrdinaryObject(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readObject0(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readObject(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?com.qunar.io.serial.SerialCode2.main(<span style="color:rgb(0,0,128)"><u>SerialCode2.java:17</u></span>)</span></div></td></tr></tbody></table>
打開文件和讀取Student對象中內容都需要Student的class對象,而虛擬機找不到Student.calss,這樣會導致拋出ClassNotFoundException異常。所以必須保證虛擬機能夠找到相關的.class文件。
從這里就可以證明得到:不能只利用序列化字節數據文件來得到原先對象,還必須對應類的.class文件。
### 3.序列化控制
默認的序列化機制并不難控制。然而,如果有特殊的需要那又該怎么辦?例如,也許考慮特殊的安全問題,而且你不希望對象的某一部分被序列化;或者一個對象還原以后,某子對象需要重新創建,從而不必將該子對象序列化。
為了應對這些特殊的情況,可通過Externalizable接口(代替實現Serializable接口)來對序列化過程進行控制。這個Externalizable接口繼承了Serializable接口,同時還增添了兩個方法:writeExternal()和readExternal()。這兩個方法會在序列化和反序列化還原過程中自動調用,以便執行一些特殊操作。
~~~
package com.qunar.io;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Fruit implements Externalizable{
public Fruit(){
System.out.println("Fruit constructor...");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Fruit writeExternal...");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Fruit readExternal...");
}
}
~~~
~~~
package com.qunar.io;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Fruit2 implements Externalizable{
Fruit2(){
System.out.println("Fruit2 constuctor...");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Fruit writeExternal...");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Fruit readExternal...");
}
}
~~~
~~~
package com.qunar.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class FruitSerialCode {
public static void main(String[] args) {
try {
// 創建FileOutputStream對象
FileOutputStream fileOutputStream = new FileOutputStream("fruit.out");
// 創建ObjectOutputStream對象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 序列化對象
Fruit fruit = new Fruit();
Fruit2 fruit2 = new Fruit2();
// 進行對象序列化 Fruit對象要實現序列化接口
System.out.println("writeObject...");
objectOutputStream.writeObject(fruit);
objectOutputStream.writeObject(fruit2);
objectOutputStream.flush();
objectOutputStream.close();
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream("fruit.out");
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
System.out.println("readFruit...");
fruit = (Fruit)objectInputStream.readObject();
System.out.println("readFruit2...");
fruit2 = (Fruit2)objectInputStream.readObject();
objectInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
運行結果:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit2?constuctor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit2...</span></div><div><span style="font-size:14pt; color:rgb(0,0,128); font-family:微軟雅黑"><u>java.io.InvalidClassException<span style="text-decoration:none; color:rgb(255,0,0)">:?com.qunar.io.Fruit2;?no?valid?constructor</span></u></span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectStreamClass.checkDeserialize(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readOrdinaryObject(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readObject0(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?java.io.ObjectInputStream.readObject(Unknown?Source)</span></div><div><span style="font-size:14pt; color:rgb(255,0,0); font-family:微軟雅黑">????at?com.qunar.io.FruitSerialCode.main(<span style="color:rgb(0,0,128)"><u>FruitSerialCode.java:36</u></span>)</span></div></td></tr></tbody></table>
Fruit和Fruit2除了細微的差別之外,幾乎完全一致。上例中沒有反序列化后Fruit2對象,并且導致了一個異常。主要是Fruit的構造函數是public的,而Fruit2的構造函數卻不是,這樣就會在反序列時拋出異常。
反序列化fruit后,會調用Fruit的默認構造函數。這與反序列一個Serializable對象不同。對于一個Serializable對象,對象完全以它存儲的二進制位為基礎,而不用調用構造函數。而對于一個Externalizable對象,所有普通的構造函數都會被調用(包括在字段定義時的初始化),然后調用readExternal()。
注意:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/>? ? ??<span style="color:#ff6820">所有默認的構造函數都會被調用,才能使Externalizable對象產生正確的行為。</span><br/></td></tr></tbody></table>
下面例子示范如何正確的序列化和反序列一個Externalizable對象:
~~~
package com.qunar.io;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Fruit implements Externalizable{
private String name;
private int num;
// 必須有默認構造函數 反序列時使用
public Fruit(){
System.out.println("Fruit default constructor...");
}
/**
* @param name
* @param num
*/
public Fruit(String name, int num) {
System.out.println("Fruit constructor...");
this.name = name;
this.num = num;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Fruit writeExternal...");
// 必須做如下操作
out.writeObject(name);
out.writeInt(num);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Fruit readExternal...");
// 必須做如下操作
name = (String)in.readObject();
num = in.readInt();
}
@Override
public String toString() {
return "name:" + name + " num:" + num;
}
}
~~~
~~~
package com.qunar.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class FruitSerialCode {
public static void main(String[] args) {
try {
// 創建FileOutputStream對象
FileOutputStream fileOutputStream = new FileOutputStream("fruit.out");
// 創建ObjectOutputStream對象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 序列化對象
Fruit fruit = new Fruit("蘋果",20);
// 進行對象序列化 Fruit對象要實現序列化接口
System.out.println("writeObject...");
objectOutputStream.writeObject(fruit);
objectOutputStream.flush();
objectOutputStream.close();
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream("fruit.out");
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
System.out.println("readFruit...");
fruit = (Fruit)objectInputStream.readObject();
System.out.println("Fruit->[" + fruit + "]");
objectInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
運行結果:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?default?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit->[name:蘋果??num:20]</span></div></td></tr></tbody></table>
可以看出,name和num只在第二個構造函數中初始化,而不是在默認的構造函數中初始化。所以說,假如不在readExternal初始化name和num,name就會為null,age就會為0,如果注釋掉代碼中"必須做如下操作"之后的代碼,反序列化之后的對象的name為null,num為0。
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?writeExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?default?constructor...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit?readExternal...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Fruit->[name:null??num:0]</span></div></td></tr></tbody></table>
如果我們從一個Externalizable對象繼承,通常需要調用基類版本的writeExternal()和readExternal()來為基類組件提供恰當的序列化和反序列化功能。因此,為了正常運行,我們不僅需要在writeExternal()方法(沒有任何默認行為來為Externalizable對象寫入任何成員對象)中將來自對象的重要信息寫入,還必須在readExternal()方法中恢復數據。
### 4.transient關鍵字
但我們對序列化進行控制時,可能某個特定屬性不想讓Java序列化機制自動保存與恢復。如果屬性表示的是我們不希望將其序列化的敏感信息(如密碼),就會遇到這種問題。即使對象中的這些信息是private屬性,一經序列化處理,人們就可以通過讀取文件或者攔截網絡傳輸的方式訪問到它。
(1)防止對象敏感信息被序列化,可以將類實現為Externalizable,像前面一樣。這樣就沒有任何東西可以自動序列化,并且可以在writeExternal()內部只對所需部分進行顯示的序列化。
(2)如果我們操作的是Serializable對象,那么所有序列化操作都會自動進行。為了進行控制,使用transient關鍵字關閉序列化操作,它的意思"不用麻煩你序列化或者反序列化數據,我自己會處理的"。
假設我們用Login類保存某個特定的登錄會話信息。登錄的合法性得到檢驗之后,我們想把數據保存下來,但不包括密碼。
~~~
package com.qunar.io;
import java.io.Serializable;
import java.util.Date;
public class Login implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Date date = new Date();
private String userName;
// 防止被序列化transient
private transient String password;
/**
* @param date
* @param userName
* @param password
*/
public Login(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
@Override
public String toString() {
return "Date:" + date + " UserName:" + userName + " Password:" + password;
}
}
package com.qunar.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class LoginSerialCode {
public static void main(String[] args) {
try {
// 創建FileOutputStream對象
FileOutputStream fileOutputStream = new FileOutputStream("login.out");
// 創建ObjectOutputStream對象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 序列化對象
Login login = new Login("xiaosi", "123");
// 進行對象序列化 Fruit對象要實現序列化接口
System.out.println("writeObject...");
objectOutputStream.writeObject(login);
objectOutputStream.flush();
objectOutputStream.close();
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream("login.out");
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
System.out.println("readFruit...");
login = (Login)objectInputStream.readObject();
System.out.println("LoginInfo->[" + login + "]");
objectInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
運行結果:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">LoginInfo->[Date:Thu?Dec?31?00:16:13?CST?2015??UserName:xiaosi?Password:</span><span style="font-size:14pt; font-family:微軟雅黑"><span style="color:#ff6820">null</span></span><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">]</span></div></td></tr></tbody></table>
date和useName(不是transient),所以它們會自動被序列化,而password是transient的,所以不會被自動保存到磁盤;另外自動序列化機制也不會去恢復它,當對象恢復時,password就會變成null。同時我們發現date字段被存儲在磁盤并且從磁盤上恢復出來,而不是重新生成。
(3)Externalizable的替代方法
如果你不使用Externalizable,我們還有一種方法。我們可以實現Serializable接口,并添加writeObject()和readObject()方法。這樣一旦進行序列化和反序列化,就會自動的分別調用這兩個方法,來代替默認的序列化機制。
~~~
package com.qunar.io;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
public class Login implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Date date = new Date();
private String userName;
// 防止被序列化transient
private transient String password;
/**
* @param date
* @param userName
* @param password
*/
public Login(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
// 必須有
private void writeObject(ObjectOutputStream stream) throws IOException{
// 默認的序列化
stream.defaultWriteObject();
// 手動完成序列化
stream.writeObject(password);
}
// 必須有
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException{
// 默認的反序列化
stream.defaultReadObject();
// 手動完成反序列化
password = (String)stream.readObject();
}
@Override
public String toString() {
return "Date:" + date + " UserName:" + userName + " Password:" + password;
}
}
~~~
~~~
package com.qunar.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class LoginSerialCode {
public static void main(String[] args) {
try {
// 創建FileOutputStream對象
FileOutputStream fileOutputStream = new FileOutputStream("login.out");
// 創建ObjectOutputStream對象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 序列化對象
Login login = new Login("xiaosi", "123");
// 進行對象序列化 Fruit對象要實現序列化接口
System.out.println("writeObject...");
objectOutputStream.writeObject(login);
objectOutputStream.flush();
objectOutputStream.close();
// 創建FileInputStream對象
FileInputStream fileInputStream = new FileInputStream("login.out");
// 創建ObjectInputStream對象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 反序列化為對象
System.out.println("readFruit...");
login = (Login)objectInputStream.readObject();
System.out.println("LoginInfo->[" + login + "]");
objectInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
~~~
運行結果:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">writeObject...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">readFruit...</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">LoginInfo->[Date:Fri?Jan?01?15:57:53?CST?2016??UserName:xiaosi?</span><span style="font-size:14pt; font-family:微軟雅黑"><span style="color:#ff6820">Password:123</span></span><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">]</span></div></td></tr></tbody></table>
相比上面實驗,password恢復出來了。在這個例子中,password字段是transient字段,用來證明非transient字段是由defaultWriteObject()方法保存,而transient字段是必須在程序中明確保存和恢復。
### 5.使用"持久化"
一個誘人的使用序列化技術的想法:存儲程序的一些狀態,以便我們隨后可以很容易的將程序恢復到當前的狀態。但是在我們能夠這樣做之前,必須回答幾個問題。如果我們將兩個對象(都具有指向第三個對象的引用)進行序列化,會發生什么狀況?當我們從它們的序列化狀態恢復這兩個對象時,第三個對象會只出現一次嗎?
~~~
package com.qunar.io;
import java.io.Serializable;
public class House implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private String location;
/**
* @param name
* @param location
*/
public House(String name, String location) {
super();
this.name = name;
this.location = location;
~~~
~~~
package com.qunar.io;
import java.io.Serializable;
public class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private House house;
/**
* 構造函數
* @param name
* @param house
*/
public Animal(String name, House house) {
super();
this.name = name;
this.house = house;
}
@Override
public String toString() {
return name + " " + house;
}
}
~~~
~~~
package com.qunar.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class AnimalSerialCode {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
House house = new House("水立方","北京海淀區");
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal("狗", house));
animals.add(new Animal("雞", house));
animals.add(new Animal("羊", house));
System.out.println("Animals->" + animals);
try {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(buf);
// 序列化
objectOutputStream.writeObject(animals);
objectOutputStream.writeObject(animals);
ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(buf2);
// 序列化
objectOutputStream2.writeObject(animals);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
List<Animal> ani1 = (List<Animal>)objectInputStream.readObject();
List<Animal> ani2 = (List<Animal>)objectInputStream.readObject();
ObjectInputStream objectInputStream2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray()));
List<Animal> ani3 = (List<Animal>)objectInputStream2.readObject();
System.out.println("Animals1->"+ani1);
System.out.println("Animals2->"+ani2);
System.out.println("Animals3->"+ani3);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
~~~
運行結果:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1098px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1097px"><br/><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Animals->[狗???com.qunar.io.House@1b15692,?雞???com.qunar.io.House@1b15692,?羊???com.qunar.io.House@1b15692]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Animals1->[狗???com.qunar.io.House@1aaf0b3,?雞???com.qunar.io.House@1aaf0b3,?羊???com.qunar.io.House@1aaf0b3]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Animals2->[狗???com.qunar.io.House@1aaf0b3,?雞???com.qunar.io.House@1aaf0b3,?羊???com.qunar.io.House@1aaf0b3]</span></div><div><span style="font-size:14pt; color:windowtext; font-family:微軟雅黑">Animals3->[狗???com.qunar.io.House@1a082e2,?雞???com.qunar.io.House@1a082e2,?羊???com.qunar.io.House@1a082e2]</span></div></td></tr></tbody></table>
我們可以通過字節數組來使用對象序列化,從而實現任何可Serializable對象的"深度復制"(意味著復制的是整個對象網,而不僅僅是基本對象及其引用)。在這個例子中,Animal對象包含House類型字段。我們創建Animals列表并將其兩次序列化,分別送至不同的流。當期被反序列化還原被打印時,我們可以看到:每次運行時對象將會處在不同的內存地址。
當然我們期望這些反序列還原后的對象地址與原來的對象地址不同,但是Animals1和Animals2卻出現了相同的地址。當恢復Animals3時,系統無法知道另一個流內的對象是第一個流內對象額別名,因此會產生完全不同的對象網。
只要將任何對象序列化到單一流中,就可以會付出與我們寫出時一樣的對象網,并且沒有任何意外重復復制的對象。如果想保存系統狀態,最安全的做法就是將其作為"原子"操作進行序列化。
- 前言
- [Hibernate開發之路](1)Hibernate配置
- [Hibernate開發之路](2)Hibernate問題
- [Hibernate開發之路](3)基礎配置
- [Hibernate開發之路](4)ID生成策略
- [Hibernate開發之路](5)聯合主鍵
- [設計模式實踐之路](1)單例模式
- [Java]UDP通信的簡單例子
- [Java]套接字地址InetAddress講解
- [Java開發之路](1)final關鍵字
- [Java開發之路](2)Java字符串
- [Java開發之路](3)Java常用類
- [Java開發之路](4)String、StringBuffer與StringBuilder詳解
- [Java開發之路](5)異常詳解
- [Java開發之路](6)File類的使用
- [Java開發之路](7)RandomAccessFile類詳解
- [Java開發之路](8)輸入流和輸出流
- [Java開發之路](9)對象序列化與反序列化
- [Java開發之路](10)DOM解析XML文檔
- [Java開發之路](11)SAX解析XML文檔
- [Java開發之路](12)JDOM和DOM4J解析XML文檔
- [Java開發之路](14)反射機制
- [Java開發之路](15)注解
- [Java開發之路](16)學習log4j日志
- [Java開發之路](18)關于Class.getResource和ClassLoader.getResource的路徑問題
- [Java開發之路](19)Long緩存問題
- [Java開發之路](20)try-with-resource 異常聲明
- [Java開發之路](21)Comparator與Comparable
- [Java]Java工程師成神之路
- [細說Java](1)圖說字符串的不變性
- [細說Java](2)Java中字符串為什么是不可變的
- [細說Java](3)創建字符串是使用&quot; &quot;還是構造函數?