序列化---Serializable與Externalizable原始碼
Serializable介面總結:
1. java.io.Serializable介面是一個標識介面,它沒有任何欄位和方法,用來表示此類可序列化; 2. 父類宣告該介面,則其與其所有子類均可序列化,都無須提供無參構造器(反序列化時不會呼叫父類或子類的無參構造器) 3. 父類不可序列化,子類宣告該介面,父類必須提供子類可訪問的無參構造器(子類有無無參構造器均可),程式才能正常執行,但是反序列化後,父類資訊均為無參構造器初始化的內容(反序列化時,會呼叫子類可訪問的父類的無參構造器,對父類部分進行初始化) 4. 父類不可序列化,子類宣告該介面,要想序列化父類資訊,子類必須重寫 原因:物件反序列化時,如果父類未實現序列化介面,則反序列出的物件會再次呼叫父類的建構函式來完成屬於父類那部分內容的初始化 https://blog.csdn.net/wanping321/article/details/70304403 5. serialVersionUID 標識序列化類的序列號,該序列號在反序列化過程中用於驗證序列化物件的傳送者和接收者是否為該物件載入了與序列化相容的類,若不同,反序列化將會導致 InvalidClassException,若沒有顯示宣告,則編譯器會計算一個,但存在不同編譯器計算值不同,產生不必要的InvalidClassException ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; static--類級別--保證是同一個類就OK 建議使用private---對宣告的該類有效,繼承無用(一個類一個號) |
package java.io; /** * Serializability of a class is enabled by the class implementing the * java.io.Serializable interface. Classes that do not implement this * interface will not have any of their state serialized or * deserialized. All subtypes of a serializable class are themselves * serializable. The serialization interface has no methods or fields * and serves only to identify the semantics of being serializable. <p> *
類通過實現 java.io.Serializable 介面以啟用其序列化功能。未實現此介面的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子型別本身都是可序列化的。序列化介面沒有方法或欄位,僅用於標識可序列化的語義。
父類宣告實現Serializable介面,父類與子類均有public無參構造器、父類與子類都沒有無參構造器、父類有子類沒有,均不會報錯,會正常執行。
序列化的類:
package test.chap9; import java.io.IOException; public class Person implements java.io.Serializable { private String name; private transient int age; public float height; public Person (String name, int age, float height){ System.out.println("有引數的構造器"); this.name = name; this.age = age; this.height = height; } // Person (){ // System.out.println("person"); // } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
測試:
package test.chap9; import java.io.*; // The serializable class Mother does not declare a static final serialVersionUID field of type long class Mother extends Person/* implements java.io.Serializable*/ { private int chirdNum; public int getChirdNum() { return chirdNum; } public void setChirdNum(int chirdNum) { this.chirdNum = chirdNum; } public Mother(String name, int age, int childNum, float height){ this(name,age,height); this.chirdNum = childNum; } public Mother(String name, int age, float height) { super(name, age, height); } // public Mother(){ // super(); // System.out.println("mother"); // } @Override public String toString() { return "mama"; } } public class TestExternalizable { public static void main(String[] args) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File("extern.txt")))); // Person p = new Person("laowang",42); Mother m = new Mother("mm", 58, 2, 1.57f); oos.writeObject(m); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("====================="); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("extern.txt")))); // Person p2 = (Person)ois.readObject(); Mother m2 = (Mother)ois.readObject(); System.out.println( m2.height + " " + m2.getChirdNum()); System.out.println(m2); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
結果:
有引數的構造器 ===================== 1.57 2 mama
繼續接著看:
* To allow subtypes of non-serializable classes to be serialized, the * subtype may assume responsibility for saving and restoring the * state of the supertype's public, protected, and (if accessible) * package fields. The subtype may assume this responsibility only if * the class it extends has an accessible no-arg constructor to * initialize the class's state.父類有一個可獲得的無參構造器來初始化父類部分的狀態 It is an error to declare a class * Serializable if this is not the case. The error will be detected at * runtime. <p> * * During deserialization, the fields of non-serializable classes will * be initialized using the public or protected no-arg constructor of * the class. A no-arg constructor must be accessible to the subclass * that is serializable. The fields of serializable subclasses will * be restored from the stream. <p> *
要允許不可序列化類的子型別序列化,可以假定該子型別負責儲存和恢復超型別的公用 (public)、受保護的 (protected) 和(如果可訪問)包 (package) 欄位的狀態。僅在子型別擴充套件的類有一個可訪問的無引數構造方法來初始化該類的狀態時,才可以假定子型別有此職責。如果不是這種情況,則宣告一個類為可序列化類是錯誤的。該錯誤將在執行時檢測到。
在反序列化過程中,將使用該類的公用或受保護的無引數構造方法初始化不可序列化類的欄位。可序列化的子類必須能夠訪問無引數構造方法。可序列化子類的欄位將從該流中恢復。
父類不能序列化,但是子類宣告實現Serializable介面,只要父類有public無參構造器,不管子類是否有無參構造器執行時都不報錯,如果父類沒有無參構造器會執行時報錯:no valid constructor。如果父類構造器用預設(包許可權)或者protected,也能正常執行;private不行。但是輸出父類資訊時為系統預設的初始化值。
如果子類有public無慘構造器,有時提示(沒有用到子類資訊時):The constructor Mother() is never used locally,並且子類有警告:The serializable class Mother does not declare a static final serialVersionUID field of type long
// 父類: package test.chap9; public class Person { private String name; private transient int age; public Person (String name, int age){ System.out.println("有引數的構造器"); this.name = name; this.age = age; } public Person (){ } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
測試:
package test.chap9; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; // The serializable class Mother does not declare a static final serialVersionUID field of type long class Mother extends Person implements java.io.Serializable { private int chirdNum; public int getChirdNum() { return chirdNum; } public void setChirdNum(int chirdNum) { this.chirdNum = chirdNum; } public Mother(String name, int age, int childNum){ this(name,age); this.chirdNum = childNum; } public Mother(String name, int age) { super(name, age); } public Mother(){} } public class TestExternalizable { public static void main(String[] args) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File("extern.txt")))); // Person p = new Person("laowang",42); Mother m = new Mother("mama", 48, 2); oos.writeObject(m); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { oos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("====================="); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("extern.txt")))); // Person p2 = (Person)ois.readObject(); Mother m2 = (Mother)ois.readObject(); System.out.println(m2.getName() + " " + m2.getAge() + " " + m2.getChirdNum()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
結果:
有引數的構造器 ===================== null 0 2
將父類的無參構造器新增內容--初始化資訊,則這些資訊會在反序列化時得到。
public Person (){ this("iii", 25); }
/*
結果:
有引數的構造器
=====================
有引數的構造器
iii 25 2
*/
要想序列化父類屬性,必須在子類中重寫writeObject和readObject方法。
blog:關於父類與子類之間的序列化關係
https://blog.csdn.net/wanping321/article/details/70304403
①當父類沒有Serializable,子類implements Serializable時,反序列化時,會呼叫父類的無參構造器(不一定是public,只要子類能訪問到)
②當父類implements Serializable,子類直接繼承過來,反序列化時,不會呼叫父類的構造器。
原因:物件反序列化時,如果父類未實現序列化介面,則反序列出的物件會再次呼叫父類的建構函式來完成屬於父類那部分內容的初始化。
當將一個父類沒有實現序列化的物件son使用ObjectOutputStream流寫到本地檔案中時,沒有能將該物件中屬於父類的部分寫入到檔案,因為ObjectOutputStream流不能將一個沒有實現序列化的類的物件寫入檔案中。當將本地檔案中儲存的son物件通過ObjectInputStream流反序列化到程式中,由於缺少屬於父類的部分資訊,則需要再次呼叫父類的構造器來完成初始化。
* When traversing a graph, an object may be encountered that does not * support the Serializable interface. In this case the * NotSerializableException will be thrown and will identify the class * of the non-serializable object. <p> *
當遍歷一個圖形時,可能會遇到不支援 Serializable 介面的物件。在此情況下,將丟擲 NotSerializableException,並將標識不可序列化物件的類。
用於自定義序列化的方法:
* Classes that require special handling during the serialization and * deserialization process must implement special methods with these exact * signatures: * * <PRE> * private void writeObject(java.io.ObjectOutputStream out) * throws IOException * private void readObject(java.io.ObjectInputStream in) * throws IOException, ClassNotFoundException; * private void readObjectNoData() * throws ObjectStreamException; * </PRE> * * <p>The writeObject method is responsible for writing the state of the * object for its particular class so that the corresponding * readObject method can restore it. The default mechanism for saving * the Object's fields can be invoked by calling * out.defaultWriteObject. The method does not need to concern * itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObject method is responsible for reading from the stream and * restoring the classes fields. It may call in.defaultReadObject to invoke * the default mechanism for restoring the object's non-static and * non-transient fields. The defaultReadObject method uses information in * the stream to assign the fields of the object saved in the stream with the
* correspondingly named fields in the current object. This handles the case * when the class has evolved to add new fields. The method does not need to * concern itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObjectNoData method is responsible for initializing the state of * the object for its particular class in the event that the serialization * stream does not list the given class as a superclass of the object being * deserialized. This may occur in cases where the receiving party uses a * different version of the deserialized instance's class than the sending * party, and the receiver's version extends classes that are not extended by * the sender's version. This may also occur if the serialization stream has * been tampered; hence, readObjectNoData is useful for initializing * deserialized objects properly despite a "hostile" or incomplete source * stream. *
writeObject 方法負責寫入特定類的物件的狀態,以便相應的 readObject 方法可以恢復它。通過呼叫 out.defaultWriteObject 可以呼叫儲存 Object 的欄位的預設機制。該方法本身不需要涉及屬於其超類或子類的狀態。通過使用 writeObject 方法或使用 DataOutput 支援的用於基本資料型別的方法將各個欄位寫入 ObjectOutputStream,狀態可以被儲存。
readObject 方法負責從流中讀取並恢復類欄位。它可以呼叫 in.defaultReadObject 來呼叫預設機制,以恢復物件的非靜態和非瞬態欄位。defaultReadObject 方法使用流中的資訊來分配流中通過當前物件中相應指定欄位儲存的物件的欄位。這用於處理類演化後需要新增新欄位的情形。該方法本身不需要涉及屬於其超類或子類的狀態。通過使用 writeObject 方法或使用 DataOutput 支援的用於基本資料型別的方法將各個欄位寫入 ObjectOutputStream,狀態可以被儲存。
在序列化流不列出給定類作為將被反序列化物件的超類的情況下,readObjectNoData 方法負責初始化特定類的物件狀態。這在接收方使用的反序列化例項類的版本不同於傳送方,並且接收者版本擴充套件的類不是傳送者版本擴充套件的類時發生。在序列化流已經被篡改時也將發生;因此,不管源流是“敵意的”還是不完整的,readObjectNoData 方法都可以用來正確地初始化反序列化的物件。
替代當前要序列化的物件:
* <p>Serializable classes that need to designate an alternative object to be * used when writing an object to the stream should implement this * special method with the exact signature: * * <PRE> * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException; * </PRE><p> * * This writeReplace method is invoked by serialization if the method * exists and it would be accessible from a method defined within the * class of the object being serialized. Thus, the method can have private, * protected and package-private access. Subclass access to this method * follows java accessibility rules. <p> *
此 writeReplace 方法將由序列化呼叫,前提是如果此方法存在,而且它可以通過被序列化物件的類中定義的一個方法訪問。因此,該方法可以擁有私有 (private)、受保護的 (protected) 和包私有 (package-private) 訪問。子類對此方法的訪問遵循 java 訪問規則。
readResolve()保護性恢復單例、列舉型別的物件,並不恢復writeReplace
* Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * * <PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE><p> * * This readResolve method follows the same invocation rules and * accessibility rules as writeReplace.<p> * * The serialization runtime associates with each serializable class a version * number, called a serialVersionUID, which is used during deserialization to * verify that the sender and receiver of a serialized object have loaded * classes for that object that are compatible with respect to serialization. * If the receiver has loaded a class for the object that has a different * serialVersionUID than that of the corresponding sender's class, then * deserialization will result in an {@link InvalidClassException}. A * serializable class can declare its own serialVersionUID explicitly by * declaring a field named <code>"serialVersionUID"</code> that must be static, * final, and of type <code>long</code>: * * <PRE> * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; * </PRE> * * If a serializable class does not explicitly declare a serialVersionUID, then * the serialization runtime will calculate a default serialVersionUID value * for that class based on various aspects of the class, as described in the * Java(TM) Object Serialization Specification. However, it is <em>strongly * recommended</em> that all serializable classes explicitly declare * serialVersionUID values, since the default serialVersionUID computation is * highly sensitive to class details that may vary depending on compiler * implementations, and can thus result in unexpected * <code>InvalidClassException</code>s during deserialization. Therefore, to * guarantee a consistent serialVersionUID value across different java compiler * implementations, a serializable class must declare an explicit * serialVersionUID value. It is also strongly advised that explicit * serialVersionUID declarations use the <code>private</code> modifier where * possible, since such declarations apply only to the immediately declaring * class--serialVersionUID fields are not useful as inherited members. Array * classes cannot declare an explicit serialVersionUID, so they always have * the default computed value, but the requirement for matching * serialVersionUID values is waived for array classes. *
序列化執行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化物件的傳送者和接收者是否為該物件載入了與序列化相容的類。如果接收者載入的該物件的類的 serialVersionUID 與對應的傳送者的類的版本號不同,則反序列化將會導致 InvalidClassException。可序列化類可以通過宣告名為 "serialVersionUID" 的欄位(該欄位必須是靜態 (static)、最終 (final) 的 long 型欄位)顯式宣告其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類未顯式宣告 serialVersionUID,則序列化執行時將基於該類的各個方面計算該類的預設 serialVersionUID 值,如“Java(TM) 物件序列化規範”中所述。不過,強烈建議 所有可序列化類都顯式宣告 serialVersionUID 值,原因是計算預設的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須宣告一個明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示宣告 serialVersionUID(如果可能),原因是這種宣告僅應用於直接宣告類 -- serialVersionUID 欄位作為繼承成員沒有用處。陣列類不能宣告一個明確的 serialVersionUID,因此它們總是具有預設的計算值,但是陣列類沒有匹配 serialVersionUID 值的要求。
如果被序列化的類沒有顯示的指定serialVersionUID標識(序列版本UID),系統會自動根據這個類來呼叫一個複雜的運算過程生成該標識。此標識是根據類名稱、介面名稱、所有公有和受保護的成員名稱生成的一個64位的Hash欄位。
* @author unascribed * @see java.io.ObjectOutputStream * @see java.io.ObjectInputStream * @see java.io.ObjectOutput * @see java.io.ObjectInput * @see java.io.Externalizable * @since JDK1.1 */ public interface Serializable { }
Externalizable介面總結:
1. Externalizable介面繼承於Serializable介面,提供了兩個方法writeExternal和readExternal,這兩個方法替代了ObjectOutputStream中的writeObject和ObjectInputStream的readObject方法。 2. 實現Externalizable介面的類的物件在反序列化時,會先呼叫該物件類的public無參構造器建立一個例項,然後呼叫 readExternal 方法。 |
package java.io; import java.io.ObjectOutput; import java.io.ObjectInput; /** * Only the identity of the class of an Externalizable instance is * written in the serialization stream and it is the responsibility * of the class to save and restore the contents of its instances. * * The writeExternal and readExternal methods of the Externalizable * interface are implemented by a class to give the class complete * control over the format and contents of the stream for an object * and its supertypes. These methods must explicitly * coordinate with the supertype to save its state. These methods supersede * customized implementations of writeObject and readObject methods.<br> * * Object Serialization uses the Serializable and Externalizable * interfaces. Object persistence mechanisms can use them as well. Each * object to be stored is tested for the Externalizable interface. If * the object supports Externalizable, the writeExternal method is called. If the * object does not support Externalizable and does implement * Serializable, the object is saved using * ObjectOutputStream. <br> When an Externalizable object is * reconstructed, an instance is created using the public no-arg * constructor, then the readExternal method called. Serializable * objects are restored by reading them from an ObjectInputStream.<br> * * An Externalizable instance can designate a substitution object via * the writeReplace and readResolve methods documented in the Serializable * interface.<br> *
Externalizable 例項類的唯一特性是可以被寫入序列化流中,該類負責儲存和恢復例項內容。 若某個要完全控制某一物件及其超型別的流格式和內容,則它要實現 Externalizable 介面的 writeExternal 和 readExternal 方法。這些方法必須顯式與超型別進行協調以儲存其狀態。這些方法將代替定製的 writeObject 和 readObject 方法實現。
Serialization 物件將使用 Serializable 和 Externalizable 介面。物件永續性機制也可以使用它們。要儲存的每個物件都需要檢測是否支援 Externalizable 介面。如果物件支援 Externalizable,則呼叫 writeExternal 方法。如果物件不支援 Externalizable 但實現了 Serializable,則使用 ObjectOutputStream 儲存該物件。
在重構 Externalizable 物件時,先使用無引數的公共構造方法建立一個例項,然後呼叫 readExternal 方法。如果沒有public無參構造器(必須是public),執行時在readExternal報錯no valid constructor。通過從 ObjectInputStream 中讀取 Serializable 物件可以恢復這些物件。
Externalizable 例項可以通過 Serializable 介面中記錄的 writeReplace 和 readResolve 方法來指派一個替代物件。
package test.chap9; import java.io.IOException; public class Person implements java.io.Externalizable { private String name; private transient int age; public float height; public Person (String name, int age, float height){ System.out.println("有引數的構造器"); this.name = name; this.age = age; this.height = height; } public Person (){ System.out.println("person"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void writeExternal(java.io.ObjectOutput out) throws IOException{ out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } public void readExternal(java.io.ObjectInput in) throws ClassNotFoundException, IOException{ this.name = ((StringBuffer)in.readObject()).reverse().toString(); this.age = in.readInt(); } }
測試:
public class TestExternalizable { public static void main(String[] args) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File("extern.txt")))); Person p = new Person("laowang",42,1.24f); oos.writeObject(p); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("====================="); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("extern.txt")))); Person p2 = (Person)ois.readObject(); System.out.println(p2.getName() + " " + p2.getAge()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
結果:
有引數的構造器 ===================== person laowang 42
* @author unascribed * @see java.io.ObjectOutputStream * @see java.io.ObjectInputStream * @see java.io.ObjectOutput * @see java.io.ObjectInput * @see java.io.Serializable * @since JDK1.1 */ public interface Externalizable extends java.io.Serializable { /** * The object implements the writeExternal method to save its contents * by calling the methods of DataOutput for its primitive values or * calling the writeObject method of ObjectOutput for objects, strings, * and arrays. * * @serialData Overriding methods should use this tag to describe * the data layout of this Externalizable object. * List the sequence of element types and, if possible, * relate the element to a public/protected field and/or * method of this Externalizable class. * * @param out the stream to write the object to * @exception IOException Includes any I/O exceptions that may occur */ void writeExternal(ObjectOutput out) throws IOException; /** * The object implements the readExternal method to restore its * contents by calling the methods of DataInput for primitive * types and readObject for objects, strings and arrays. The * readExternal method must read the values in the same sequence * and with the same types as were written by writeExternal. * * @param in the stream to read data from in order to restore the object * @exception IOException if I/O errors occur * @exception ClassNotFoundException If the class for an object being * restored cannot be found. */ void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
註解:@serialData(1.2):語法[@serialData data-description]
data-description建立資料(尤其是writeObject方法所寫入的可選資料和Externalizable.writeExternal方法寫入的全部資料)序列和型別的文件,@serialData標記可用於writeObject、readObject、writeExternal和readExternal方法的文件註釋中