Java學習中關於實現Serializable接口
為了保存在內存中的各種對象的狀態(也就是實例變量,不是方法),並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java為我們提供一種很好保存對象狀態的機制,那就是序列化。
簡單來說序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化,流的概念這里不用多說(就是I/O),我們可以對流化后的對象進行讀寫操作,也可將流化后的對象傳輸於網絡之間!在對對象流進行讀寫操作時會引發一些問題,而序列化機制正是用來解決這些問題的!
它面向那些實現了Serializable接口的對象,可將它們轉換成一系列字節,並可在以后完全恢復回原來的樣子。這一過程亦可通過網絡進行。這意味着序列化機制能自動補償操作系統間的差異。換句話說,可以先在Windows機器上創建一個對象,對其序列化,然后通過網絡發給一台Unix機器,然后在那里准確無誤地重新“裝配”。不必關心數據在不同機器上如何表示,也不必關心字節的順序或者其他任何細節。
那么我們在什么情況下需要使用到Serializable接口呢??
1、當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
2、當你想用套接字在網絡上傳送對象的時候;
3、當你想通過RMI傳輸對象的時候;
下面我們由一個問題的引出當我們想把的內存中的對象狀態保存到一個文件中或者數據庫中時候,Serializable接口能幫我們做些什么?
如上所述,讀寫對象會有什么問題呢?比如:我要將對象寫入一個磁盤文件而后再將其讀出來會有什么問題嗎?別急,其中一個最大的問題就是對象引用!舉個例子來說:假如我有兩個類,分別是A和B,B類中含有一個指向A類對象的引用,現在我們對兩個類進行實例化{ A a = new A(); B b = new B(); },這時在內存中實際上分配了兩個空間,一個存儲對象a,一個存儲對象b,接下來我們想將它們寫入到磁盤的一個文件中去,就在寫入文件時出現了問題!因為對象b包含對對象a的引用,所以系統會自動的將a的數據復制一份到b中,這樣的話當我們從文件中恢復對象時(也就是重新加載到內存中)時,內存分配了三個空間,而對象a同時在內存中存在兩份,想一想后果吧,如果我想修改對象a的數據的話,那不是還要搜索它的每一份拷貝來達到對象數據的一致性,這不是我們所希望的!
以下序列化機制的解決方案:
1.保存到磁盤的所有對象都獲得一個序列號(1, 2, 3等等)
2.當要保存一個對象時,先檢查該對象是否被保存了。
3.如果以前保存過,只需寫入"與已經保存的具有序列號x的對象相同"的標記,否則,保存該對象通過以上的步驟序列化機制解決了對象引用的問題!
在對對象進行實例化的過程中相關注意事項
a)序列化時,只對對象的狀態進行保存,而不管對象的方法;
b)當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口;
c)當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化;
d)並非所有的對象都可以序列化,至於為什么不可以,有很多原因了,比如:
1.安全方面的原因,比如一個對象擁有private,public等field,對於一個要傳輸的對象,比如寫到文件,或者進行RMI傳輸 等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的。
2. 資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現。
序列化的實現
將需要被序列化的類實現Serializable接口,該接口沒有需要實現的方法,implements Serializable只是為了標注該對象是可被序列化的,然后使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,接着,使用ObjectOutputStream對象的writeObject(Object obj)方法就可以將參數為obj的對象寫出(即保存其狀態),要恢復的話則用輸入流。
package serializable;
import java.io.Serializable;
public class Employee implements Serializable {
private String name;
private double salary;
public Employee(String name, double salary) {
super();
// TODO Auto-generated constructor stub
this.name = name;
this.salary = salary;
}
public void raiseSalary(double byPercent){
double temp = salary * byPercent / 100;
salary += temp;
}
public String toString() {
// TODO Auto-generated method stub
return getClass().getName() +
"[ Name = " + name + ", salary = " + salary +"]";
}
package serializable;
public class Manager extends Employee {
private Employee secretary;
public Manager(String name, double salary) {
super(name, salary);
// TODO Auto-generated constructor stub
secretary = null;
}
public Employee getSecretary() {
return secretary;
}
public void setSecretary(Employee secretary) {
this.secretary = secretary;
}
public String toString() {
// TODO Auto-generated method stub
return super.toString() + "[ secretary = " + secretary +"]";
}
}
package serializable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args){
Employee employee = new Employee("LiLei", 1000);
Manager manager1 = new Manager("Jim", 20000);
manager1.setSecretary(employee);
Employee[] staff = new Employee[2];
staff[0] = employee;
staff[1] = manager1;
try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
oos.writeObject(staff);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.dat"));
Employee[] newStaff = (Employee[])ois.readObject();
ois.close();
newStaff[0].raiseSalary(1000);
for(int i=0; i<newStaff.length; i++)
System.out.println(newStaff[i]);
}catch(Exception e)
{
e.printStackTrace();
}
}
}
修改默認的序列化機制
在序列化的過程中,有些數據字段我們不想將其序列化,對於此類字段我們只需要在定義時給它加上transient關鍵字即可,對於transient字段序列化機制會跳過不會將其寫入文件,當然也不可被恢復。但有時我們想將某一字段序列化,但它在SDK中的定義卻是不可序列化的類型,這樣的話我們也必須把他標注為transient,可是不能寫入又怎么恢復呢?好在序列化機制為包含這種特殊問題的類提供了如下的方法定義:
private void readObject(ObjectInputStream in) throws
IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws
IOException;
(注:這些方法定義時必須是私有的,因為不需要你顯示調用,序列化機制會自動調用的)
使用以上方法我們可以手動對那些你又想序列化又不可以被序列化的數據字段進行寫出和讀入操作。
下面是一個典型的例子,java.awt.geom包中的Point2D.Double類就是不可序列化的,因為該類沒有實現Serializable接口,在我的例子中將把它當作LabeledPoint類中的一個數據字段,並演示如何將其序列化
package transientTest;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class LabeledPoint implements Serializable {
private String label;
transient private Point2D.Double point;
public LabeledPoint(String label, double x, double y) {
super();
// TODO Auto-generated constructor stub
this.label = label;
this.point = new Point2D.Double(x,y);
}
private void writeObject(ObjectOutputStream oos)throws IOException{
oos.defaultWriteObject();
oos.writeDouble(point.getX());
oos.writeDouble(point.getY());
}
private void readObject(ObjectInputStream ois)throws IOException, ClassNotFoundException{
ois.defaultReadObject();
double x = ois.readDouble() + 1.0;
double y = ois.readDouble() + 1.0;
point = new Point2D.Double(x,y);
}
public String toString() {
// TODO Auto-generated method stub
return getClass().getName() + "[ Label = " + label + ", point.getX() = "
+ point.getX() + ", point.getY() = " + point.getY() + "]";
}
}
package transientTest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class transientTest {
public static void main(String[] args){
LabeledPoint label = new LabeledPoint("Book", 5.0, 5.0);
try{
System.out.println("before:\n" + label);
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("c:\\label.txt"));
oos.writeObject(label);
oos.close();
System.out.println("after:\n" + label);
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("c:\\label.txt"));
LabeledPoint label1 = (LabeledPoint)ois.readObject();
ois.close();
System.out.println("after add 1.0:\n" + label);
}catch(Exception e)
{
e.printStackTrace();
}
}
}
———————————————————————————————————————
就其本身來說,對象的序列化是非常有趣的,因為利用它可以實現“有限持久化”。請記住“持久化”意味着對象的“生存時間”並不取決於程序是否正在執行——它存在或“生存”於程序的每一次調用之間。通過序列化一個對象,將其寫入磁盤,以后在程序重新調用時重新恢復那個對象,就能圓滿實現一種“持久”效果。之所以稱其為“有限”,是因為不能用某種“persistent”(持久)關鍵字簡單地定義一個對象,並讓系統自動照看其他所有細節問題(盡管將來可能成為現實)。相反,必須在自己的程序中明確地序列化和組裝對象。
語言里增加了對象序列化的概念后,可提供對兩種主要特性的支持。Java 1.1的“遠程方法調用”(RMI)使本來存在於其他機器的對象可以表現出好像就在本地機器上的行為。將消息發給遠程對象時,需要通過對象序列化來傳輸參數和返回值。
對象的序列化也是Java Beans必需的,后者由Java 1.1引入。使用一個Bean時,它的狀態信息通常在設計期間配置好。程序啟動以后,這種狀態信息必須保存下來,以便程序啟動以后恢復;具體工作由對象序列化完成。
對象的序列化處理非常簡單,只需對象實現了Serializable接口即可(該接口僅是一個標記,沒有方法)。在Java 1.1中,許多標准庫類都發生了改變,以便能夠序列化——其中包括用於基本數據類型的全部封裝器、所有集合類以及其他許多東西。甚至Class對象也可以序列化。
為序列化一個對象,首先要創建某些OutputStream對象,然后將其封裝到ObjectOutputStream對象內。此時,只需調用writeObject()即可完成對象的序列化,並將其發送給OutputStream。相反的過程是將一個InputStream封裝到ObjectInputStream內,然后調用readObject()。和往常一樣,我們最后獲得的是指向一個上溯造型Object的句柄,所以必須下溯造型,以便能夠直接設置。
對象序列化特別“聰明”的一個地方是它不僅保存了對象的“全景圖”,而且能追蹤對象內包含的所有句柄並保存那些對象;接着又能對每個對象內包含的句柄進行追蹤;以此類推。我們有時將這種情況稱為“對象網”,單個對象可與之建立連接。而且它還包含了對象的句柄數組以及成員對象。若必須自行操縱一套對象序列化機制,那么在代碼里追蹤所有這些鏈接時可能會顯得非常麻煩。在另一方面,由於Java對象的序列化似乎找不出什么缺點,所以請盡量不要自己動手,讓它用優化的算法自動維護整個對象網。下面這個例子對序列化機制進行了測試。它建立了許多鏈接對象的一個“Worm”(蠕蟲),每個對象都與Worm中的下一段鏈接,同時又與屬於不同類(Data)的對象句柄數組鏈接:
//: Worm.java
// Demonstrates object serialization in Java 1.1
import java.io.*;
class Data implements Serializable {
private int i;
Data(int x) { i = x; }
public String toString() {
return Integer.toString(i);
}
}
public class Worm implements Serializable {
// Generate a random int value:
private static int r() {
return (int)(Math.random() * 10);
}
private Data[] d = {
new Data(r()), new Data(r()), new Data(r())
};
private Worm next;
private char c;
// Value of i == number of segments
Worm(int i, char x) {
System.out.println( " Worm constructor: " + i);
c = x;
if(--i > 0)
next = new Worm(i, (char)(x + 1));
}
Worm() {
System.out.println( "Default constructor ");
}
public String toString() {
String s = ": " + c + "( ";
for(int i = 0; i < d.length; i++)
s += d[i].toString();
s += ") ";
if(next != null)
s += next.toString();
return s;
}
public static void main(String[] args) {
Worm w = new Worm(6, 'a ');
System.out.println( "w = " + w);
try {
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream( "worm.out "));
out.writeObject( "Worm storage ");
out.writeObject(w);
out.close(); // Also flushes output
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream( "worm.out "));
String s = (String)in.readObject();
Worm w2 = (Worm)in.readObject();
System.out.println(s + ", w2 = " + w2);
} catch(Exception e) {
e.printStackTrace();
}
try {
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
ObjectOutputStream out =
new ObjectOutputStream(bout);
out.writeObject( "Worm storage ");
out.writeObject(w);
out.flush();
ObjectInputStream in =
new ObjectInputStream(
new ByteArrayInputStream(
bout.toByteArray()));
String s = (String)in.readObject();
Worm w3 = (Worm)in.readObject();
System.out.println(s + ", w3 = " + w3);
} catch(Exception e) {
e.printStackTrace();
}
}
}
