序列化---Serializable与Externalizable源码
Posted datamining-bio
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了序列化---Serializable与Externalizable源码相关的知识,希望对你有一定的参考价值。
Serializable接口总结:
1. java.io.Serializable接口是一个标识接口,它没有任何字段和方法,用来表示此类可序列化; 2. 父类声明该接口,则其与其所有子类均可序列化,都无须提供无参构造器(反序列化时不会调用父类或子类的无参构造器) 3. 父类不可序列化,子类声明该接口,父类必须提供子类可访问的无参构造器(子类有无无参构造器均可),程序才能正常运行,但是反序列化后,父类信息均为无参构造器初始化的内容(反序列化时,会调用子类可访问的父类的无参构造器,对父类部分进行初始化) 4. 父类不可序列化,子类声明该接口,要想序列化父类信息,子类必须重写writeObject和readObject方法,将可访问的父类信息序列化 原因:对象反序列化时,如果父类未实现序列化接口,则反序列出的对象会再次调用父类的构造函数来完成属于父类那部分内容的初始化 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方法的文档注释中
以上是关于序列化---Serializable与Externalizable源码的主要内容,如果未能解决你的问题,请参考以下文章
序列化---Serializable与Externalizable源码
序列化与反序列化Serializable —— Java原生态方法
序列化与反序列化总结(Serializable和Parcelable)