序列化是一種對象持久化的手段,普遍應用在網絡傳輸、RMI等場景中。Java平台允許我們在記憶體中建立可複用的Java對象,但一般情況下,隻有當JVM處于運作時,這些對象才可能存在,即:這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運作之後能夠儲存(持久化)指定的對象,并在将來重新讀取被儲存的對象。Java對象序列化就能夠幫助我們實作該功能。使用Java對象序列化,在儲存對象時,會把其狀态儲存為一組位元組,在未來,再将這些位元組組裝成對象。必須注意:對象序列化儲存的是對象的”狀态”,即它的成員變量,對象序列化不會關注類中的靜态變量。
1. 如何序列化和反序列化?
一個Java類必須實作java.io.Serializable接口,這個類的對象才可以被序列化。
package aty;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Student implements Serializable {
private int id;
private String name;
private int age;
private boolean sex;
public Student(int id, String name, int age, boolean sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age
+ ", sex=" + sex + "]";
}
}
之後我們可以通過ObjectOutputStream和ObjectInputStream對對象進行序列化及反序列化。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import aty.Student;
public class Utils {
public static void main(String[] args) throws Exception {
String file = "c:/stu.obj";
Student stu = new Student(1, "aty", 27, true);
serialize(file, stu);
Object o1 = deserialize(file);
// Student [id=1, name=aty, age=27, sex=true]
System.out.println(o1);
}
public static void serialize(String filePath, Object object)
throws IOException {
FileOutputStream fout = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(object);
oos.close();
}
public static Object deserialize(String filePath) throws Exception {
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Object result = ois.readObject();
ois.close();
return result;
}
}
2. serialVersionUID作用是什麼?
上面的Student實作了Serializable接口,但是沒有添加serialVersionUID,在eclipse下會給出警告:
The serializable class Student does not declare a static final serialVersionUID field of type long
解決這個警告很簡單:讓eclipse自動幫我們添加一個serialVersionUID。

// 預設的serialVersionUID
private static final long serialVersionUID = 1L;
// eclipse按照一定算法生産的serialVersionUID
private static final long serialVersionUID = 6990027678698020472L;
我們平常一般都是用第一種方式來解決這個警告的。那serialVersionUID作用是什麼呢?我們知道:如果我們将aty.Student這個類的對象序列号到c:/stu.obj檔案中,那麼當我們反序列化的時候,如果classpath沒有這個類會報錯:
可以看到:在序列化和反序列化的時候, 類的全路徑必須完全一緻。現在我們做一個這樣的實驗: 1.設定serialVersionUID=1後,将student對象序列化到檔案中 2.将serialVersionUID修改成2後,從檔案中反序列化student對象。
那麼我們會遇到下面的異常:
也就是說:在序列化和反序列化的過程中,類不僅路徑要完全一緻,serialVersionUID也必需相同。這就是這個字段的用處。
3. transient關鍵字的作用是什麼?
我們知道可以使用transient關鍵字來修飾類的成員變量:
private transient int age;
java序列化的時候不會儲存類的靜态字段的值,如果一個屬性有transient關鍵字,那麼這個屬性的值會在序列化的時候被忽略。transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到檔案中,在被反序列化後,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
4. 反序列化與構造函數
在進行反序列化的時候,并沒有調用類的構造方法,而是直接根據他們的序列化資料在記憶體中建立新的對象。java中建立一個對象并不一定非要調用構造函數,比如反序列化的時候,比如用Unsafe建立對象的時候。
5. 序列化與繼承
如果A類繼承了B類,如果A和B都實作了Serializable接口,那麼序列化A對象的時候會自動将其父類中的字段也進行序列化。如果A實作了Serializable接口,而如果B沒有實作Serializable接口,那麼B一定要提供無參構造函數否則反序列的時候會報錯:
6. 反序列化單例對象與readResolve
先看下面這個例子:
package aty;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private Sides side;
public Student(int id, String name, Sides side) {
this.id = id;
this.name = name;
this.side = side;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", side=" + side + "]";
}
public Sides getSide() {
return side;
}
}
package aty;
import java.io.Serializable;
public class Sides implements Serializable {
private static final long serialVersionUID = 1L;
private int value;
private Sides(int newVal) {
value = newVal;
}
private static final int LEFT_VALUE = 1;
private static final int RIGHT_VALUE = 2;
private static final int TOP_VALUE = 3;
private static final int BOTTOM_VALUE = 4;
public static final Sides LEFT = new Sides(LEFT_VALUE);
public static final Sides RIGHT = new Sides(RIGHT_VALUE);
public static final Sides TOP = new Sides(TOP_VALUE);
public static final Sides BOTTOM = new Sides(BOTTOM_VALUE);
@Override
public String toString() {
switch (value) {
case 1:
return "left";
case 2:
return "right";
case 3:
return "top";
case 4:
return "bottom";
}
return "error";
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import aty.Sides;
import aty.Student;
public class Utils {
public static void main(String[] args) throws Exception {
serialize("c:/1.obj", new Student(1, "11", Sides.LEFT));
serialize("c:/2.obj", new Student(2, "22", Sides.LEFT));
Student s1 = (Student) deserialize("c:/1.obj");
Student s2 = (Student) deserialize("c:/2.obj");
System.out.println(s1.getSide() == s2.getSide());
}
public static void serialize(String filePath, Object object)
throws IOException {
FileOutputStream fout = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(object);
oos.close();
}
public static Object deserialize(String filePath) throws Exception {
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Object result = ois.readObject();
ois.close();
return result;
}
}
可以看到執行結果是false,也就是說反序列化單例對象的時候出現了問題。我們怎麼樣才能正确地反序列化單例對象呢?使用readResolve,該方法允許class在反序列化傳回對象前替換、解析在流中讀出來的對象。實作readResolve方法,一個class可以直接控制反序化傳回的類型和對象引用。
package aty;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Sides implements Serializable {
private static final long serialVersionUID = 1L;
private int value;
private Sides(int newVal) {
value = newVal;
}
private static final int LEFT_VALUE = 1;
private static final int RIGHT_VALUE = 2;
private static final int TOP_VALUE = 3;
private static final int BOTTOM_VALUE = 4;
public static final Sides LEFT = new Sides(LEFT_VALUE);
public static final Sides RIGHT = new Sides(RIGHT_VALUE);
public static final Sides TOP = new Sides(TOP_VALUE);
public static final Sides BOTTOM = new Sides(BOTTOM_VALUE);
@Override
public String toString() {
switch (value) {
case 1:
return "left";
case 2:
return "right";
case 3:
return "top";
case 4:
return "bottom";
}
return "error";
}
private Object readResolve() throws ObjectStreamException {
switch (value) {
case LEFT_VALUE:
return LEFT;
case RIGHT_VALUE:
return RIGHT;
case TOP_VALUE:
return TOP;
case BOTTOM_VALUE:
return BOTTOM;
}
return null;
}
}
我們添加了一個readResolve,當反序列化的時候,這個方法會自動被調用。
7. writeObject和readObject定制序列化
如果一個實作了Serializable接口的類,提供了writeObject 和 readObject方法,那麼這2個方法會自動被java調用。
package aty;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private transient String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeUTF(name);
System.out.println("serialized");
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
name = ois.readUTF();
System.out.println("deserialized");
}
}
我們将name字段标記成transient的了,但是又通過代碼将name序列化到檔案中了。我們舉的這個例子沒有什麼實際用處,最典型的應用莫過于JDK中的ArrayList了,可以參考這篇“深入分析Java的序列化與反序列化”文章。
8.使用Externalizable
上面我們看到可以使用writeObject和readObject來控制序列化的過程,Externalizable功能與之類似。使用readObject和writeObject,我們必須自己寫函數簽名。實作Externalizable接口,可以自動生成函數簽名。
package aty;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Student implements Externalizable {
private static final long serialVersionUID = 1L;
private int id;
private transient String name;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeUTF(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
id = in.readInt();
name = in.readUTF();
}
}
參考文章:
深入分析Java的序列化與反序列化 http://ms.csdn.net/geek/54761
序列化-了解readResolve() http://blog.csdn.net/haydenwang8287/article/details/5964130