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',age=30}}
--- 序列化物件 ---
--- Cat2:writeExternal ---
--- 反序列化物件 ---
Cat2 無參構造器 --->
--- Cat2:readExternal ---
--- 反序列化物件後 ---
cat : Cat2{name='fugui',minion=Person{id='null',age=30}}
如果將Cat2類中標註的 code1 與 code2 程式碼下面的兩行程式碼均註釋掉,就不再可以序列化及反序列化了:
註釋掉後的執行結果:
--- 初始化物件 ---
--- Person 無參構造器 ---
Person : Person{id='01',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}
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。