本文地址:JAVA 序列化 Serializable、Externalizable及其區別
序列化簡介
Java 的對象序列化將那些實現 Serializable 接口的對象轉換成一個字節序列,並能在之后將這個字節序列完全恢復為原來的對象。
這就意味着 Java 對象在網絡上的傳輸可以不依賴於當前計算機的操作系統,就可以將對象進行傳遞,這也是Java跨平台的一種體現。
Java 對象的序列化主要支持兩種特性:
1、Java的遠程方法調用(Remote Method Invocation RMI);
2、對於 JavaBean 來說,序列化也是必須的。
要序列化一個對象,需要創建一個 OutputStream 對象,然后將其封裝在 ObjectOutputStream 對象中,再調用 writeObject() 方法就可以完成對象的序列化(也是在這一步進行序列化);反序列化(將一個序列還原為一個對象)就是該過程的反過程:創建一個 InputStream 對象,將其封裝在 ObjectInputStream 對象中,使用 readObject() 方法將序列反序列化為對象,當然這是一個Object類型的對象,需要向下轉型為我們需要的類型(如果該類型不在本地,會導致反序列化失敗,ClassNotFoundException )。
先說結論
序列化有以下方式:
1、實現 Serializable 接口:
2、實現 Externalizable 接口,並重寫 writeExternal() readExternal() 方法;
3、(即下文中的 Externalizable 的替代方式進行序列化)如果不想實現Externalizable 接口,又想按照自己的規則進行序列化,可以實現 Serializable 接口,並在該類中添加(添加,不是覆蓋、實現)名為 writeExternal() readExternal() 方法,且這兩個方法必須為下面這兩個准確的方法簽名:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException;
一、三種方式完成序列化
1、實現 Serializable 接口序列化
這種方式最為常用且常見,只需要對需要序列化的類實現 Serializable 即可,對於不希望進行序列化的,可以使用 transient 關鍵詞進行修飾(即瞬時變量)。
這種方式序列化的特征:
1、 Serializable 接口僅僅是一個標記接口,不包含任何方法;
2、對於Serializable對象來說,對象完全以它存儲的二進制位為基礎來構造,(反序列化)不會調用構造器。
2、實現 Externalizable 接口序列化
這種方式可以實現序列化的完全自定義:所有成員變量是否序列化都需要在 writeExternal()、readExternal()
方法中寫出;且可以完全自定義序列化方式(在 writerExternal()、readExternal()方法中)。當然,實現 Externalizable 接口必須要重寫這兩個方法。
這種方式序列化的特征:
1、必須重寫 writerExternal()、readExternal()兩個方法,並在兩個方法中寫出所有需要序列化的成員變量;
2、對於 Externalizable對象來說,必須要有無參public構造器,不然會報出 InvalidClassException 異常。
3、 Externalizable 的替代方式進行序列化
讓 ObjectOutputStream 和 ObjectInputStream 對象的 writeObject() 方法和 readObject() 方法調用我們編寫的這兩個方法。
如果想在這種方式中也調用原有默認提供的方式,可以在 writeObject()
中調用: s.defaultWriteObject();
,在 readObject()
中調用 s.defaultReadObject();
。 這部分代碼可以查看 ArrayList
源碼。
二、測試代碼
1、 Serializable 對象反序列化,不調用任何構造器
Serializable 對象反序列化不調用任何構造器,包括默認構造器,整個對象都是從 InputStream 中取得數據恢復過來的
主測試類 Dogs
public class Dogs {
public static void main(String[] args) throws Exception {
// 創建對象
System.out.println("--- 創建對象 ---");
Dog1 d1 = new Dog1("pidan",4.0);
Dog2 d2 = new Dog2("duanwu","black");
// 序列化
System.out.println("--- 序列化 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/dogs.out"));
oos.writeObject(d1);
oos.writeObject(d2);
System.out.println("--- 反序列化 ---");
// 反序列化 不會調用任何構造器
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/dogs.out"));
Dog1 o1 = (Dog1) ois.readObject();
Dog2 o2 = (Dog2) ois.readObject();
System.out.println("反序列化 o1 : " + o1);
System.out.println("反序列化 o2 : " + o2);
}
}
Serializable 對象 Dog1 Dog2 類
class Dog1 implements Serializable {
private static final long serialVersionUID = -7101743601344663182L;
private String name;
private Double weight;
public Dog1(String name, Double weight) {
System.out.println("Dog1 構造器運行 ---");
this.name = name;
this.weight = weight;
System.out.println("Dog1 : " + this);
}
// 省略get、set、toString方法
}
public class Dog2 implements Serializable {
private static final long serialVersionUID = -5462607652670703938L;
private String name;
private String color;
public Dog2(String name, String color) {
System.out.println("Dog2 構造器運行 ---");
this.name = name;
this.color = color;
System.out.println("Dogs2 : " + this);
}
// 省略get、set、toString方法
}
運行結果:
--- 創建對象 ---
Dog1 構造器運行 ---
Dog1 : Dog1{name='pidan', weight=4.0}
Dog2 構造器運行 ---
Dogs2 : Dog2{name='duanwu', color='black'}
--- 序列化 ---
--- 反序列化 ---
反序列化 o1 : Dog1{name='pidan', weight=4.0}
反序列化 o2 : Dog2{name='duanwu', color='black'}
再最后取出對象時,完全沒有調用到其任何構造器。
2、無參構造器對 Externalizable 對象序列化的影響
主測試代碼:
public class Persons {
public static void main(String[] args) throws Exception {
// 創建對象
System.out.println("Init Objects");
Person1 p1 = new Person1();
Person2 p2 = new Person2();
// 存儲在磁盤上
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/person.out"));
os.writeObject(p1);
os.writeObject(p2);
os.flush();
os.close();
// 取出
ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/person.out"));
System.out.println("取出p1: ");
p1 = (Person1) is.readObject();
p2 = (Person2) is.readObject();
}
}
Externalizable 對象:Perion1 Persion2
public class Person1 implements Externalizable {
public Person1(){
System.out.println("Person1 構造器---");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Person1 writeExternal ---");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Person1 readExternal ---");
}
}
class Person2 implements Externalizable{
// 注意不是public
Person2(){
System.out.println("Person2 構造器 ---");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Person2 writeExternal ---");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Person2 readExternal ---");
}
}
Person2 默認構造器不是 public 的運行結果:
Init Objects
Person1 構造器---
Person2 構造器 ---
Person1 writeExternal ---
Person2 writeExternal ---
取出p1:
Person1 構造器---
Person1 readExternal ---
Exception in thread "main" java.io.InvalidClassException: ...serializableAndexternalizable.Person2; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at ...serializableAndexternalizable.Persons.main(Persons.java:29)
Process finished with exit code 1
將 Person2 構造器改為 public 后:
Init Objects
Person1 構造器---
Person2 構造器 ---
Person1 writeExternal ---
Person2 writeExternal ---
取出p1:
Person1 構造器---
Person1 readExternal ---
Person2 構造器 ---
Person2 readExternal ---
3、使用 Externalizable 對象實現序列化
主測試類 Cats :
public class Cats {
public static void main(String[] args) throws Exception {
// 初始化對象
System.out.println("--- 初始化對象 ---");
Person person = new Person("01", "老王", 30);
Cat2 cat = new Cat2("fugui", person);
// 序列化
System.out.println("--- 序列化對象 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/cats.out"));
oos.writeObject(cat);
System.out.println("--- 反序列化對象 ---");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/cats.out"));
cat = (Cat2) ois.readObject();
System.out.println("--- 反序列化對象后 ---");
System.out.println("cat : " + cat);
}
}
Externalizable 對象: Cat2 ;Serializable 對象:Person :
public class Person implements Serializable {
private static final long serialVersionUID = -822166081906894628L;
private transient String id;
private String name;
private int age;
public Person() {
System.out.println("--- Person 無參構造器 ---");
}
public Person(String id, String name, int age) {
System.out.println("--- Person 無參構造器 ---");
this.id = id;
this.name = name;
this.age = age;
System.out.println("Person : " + this);
}
// 省略get、set、toString方法
}
class Cat2 implements Externalizable {
private static final long serialVersionUID = 1102930161606017855L;
private String name;
private Person minion;
public Cat2() {
System.out.println("Cat2 無參構造器 --->");
}
public Cat2(String name, Person minion) {
System.out.println("Cat2 有參構造器 --->");
this.name = name;
this.minion = minion;
System.out.println("Cat2 : " + this);
}
// 省略get、set、toString方法
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("--- Cat2:writeExternal ---");
// code1
out.writeObject(this.minion);
out.writeObject(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("--- Cat2:readExternal ---");
// code2
this.minion = (Person) in.readObject();
this.name = (String) in.readObject();
}
}
運行結果:
可以注意到Person的成員變量id在使用了 transient 關鍵詞修飾后,就不再序列化該字段了。
--- 初始化對象 ---
--- Person 無參構造器 ---
Person : Person{id='01', name='老王', age=30}
Cat2 有參構造器 --->
Cat2 : Cat2{name='fugui', minion=Person{id='01', name='老王', age=30}}
--- 序列化對象 ---
--- Cat2:writeExternal ---
--- 反序列化對象 ---
Cat2 無參構造器 --->
--- Cat2:readExternal ---
--- 反序列化對象后 ---
cat : Cat2{name='fugui', minion=Person{id='null', name='老王', age=30}}
如果將Cat2類中標注的 code1 與 code2 代碼下面的兩行代碼均注釋掉,就不再可以序列化及反序列化了:
注釋掉后的運行結果:
--- 初始化對象 ---
--- Person 無參構造器 ---
Person : Person{id='01', name='老王', age=30}
Cat2 有參構造器 --->
Cat2 : Cat2{name='fugui', minion=Person{id='01', name='老王', age=30}}
--- 序列化對象 ---
--- Cat2:writeExternal ---
--- 反序列化對象 ---
Cat2 無參構造器 --->
--- Cat2:readExternal ---
--- 反序列化對象后 ---
cat : Cat2{name='null', minion=null}
4、使用 Externalizable 對象替代方式實現序列化
替代方式就是實現 Serializable 接口,並且添加 writeObject(),readObject() 兩個方法注意這兩個方法必須有准確的方法特征簽名,在這兩個方法中編寫自定義方式實現序列化和反序列化。
class Mouse implements Serializable {
private static final long serialVersionUID = -3278535893876444138L;
private String name;
private int i;
public Mouse() {
System.out.println("Mouse 無參構造器 ---");
}
public Mouse(String name, int i) {
System.out.println("Mouse 有參構造器 ---");
this.name = name;
this.i = i;
System.out.println("Mouse : " + this);
}
// 方法特征簽名必須完全一致
private void writeObject(ObjectOutputStream stream) throws IOException {
// stream.defaultWriteObject();// 可以選擇執行默認的writeObject()
System.out.println("--- 這是自定義的writeExternal方法 ---");
stream.writeObject(this.name);
stream.writeInt(this.i);
}
// 方法特征簽名必須完全一致
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {
// stream.defaultReadObject(); // 可以選擇執行默認的readObject()
System.out.println("--- 這是自定義的readExternal方法 ---");
this.name = (String)stream.readObject();
this.i = stream.readInt();
}
// 省略get、set、toString方法
}
主測試類:
public class Mouses {
public static void main(String[] args) throws Exception {
// 創建對象
System.out.println("--- 創建對象 ---");
Mouse m1 = new Mouse("zhizhi", 2);
// 序列化
System.out.println("--- 序列化 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/mouse.out"));
oos.writeObject(m1);
// 反序列化
System.out.println("--- 反序列化 ---");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/mouse.out"));
// 反序列化結果
System.out.println("--- 反序列化結果 ---");
m1 = (Mouse) ois.readObject();
System.out.println(" zhizhi : " + m1);
}
}
運行結果
--- 創建對象 ---
Mouse 有參構造器 ---
Mouse : Mouse{name='zhizhi', i=2}
--- 序列化 ---
--- 這是自定義的writeExternal方法 ---
--- 反序列化 ---
--- 反序列化結果 ---
--- 這是自定義的readExternal方法 ---
zhizhi : Mouse{name='zhizhi', i=2}
參考:《JAVA編程思想(第四版)》
完