一、基本概念
Serializable接口:
1. 实现了Serializable接口的类,可以进行序列化和反序列化;没有实现这个接口的类的任何(state)状态/域或者属性值不能被序列化
2. All subtypes of a serializable class are themselves serializable. 所有实现了序列话接口的类的子类是可以被序列化的
3. 为了让不能序列化的类的子类能够被序列化,子类要能够保存和恢复(restore)父类的public/protected和package fields;只有在父类具有无参构造函数去初始化其域的情况下,子类才能保存和恢复父类的public/protectes和package fields;如果不是这种情况,声明一个可序列化的类是有问题的;并且在运行期即可以检测到这个错误;
4.在反序列化的过程中,不能序列化的类对应的域,将会用这个类的public或者protected修饰的无参构造方法进行初始化;无参的构造方法必须是可序列化的子类有访问权限的;可序列化子类的域将从流中恢复回来。
5.在序列化和反序列化的过程中,这些类都需要经过特殊的处理,也就是需要去实现特殊的方法:
<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>
5.1 WriteObject方法:
1) WriteObject方法用来将特定类的对象的域写入,这样对应的可读对象可以将这些域恢复回来。
2)默认的存储对象域的机制可以通过调用方法out.defaultWriteObject()来实现,这个方法不需要去将它自己和属于父类或者子类的域相关联起来
3)类的状态通过调用writeObject()方法或者用数据输出(DataOutput)所支持的针对原生(primitive)数据类型的方法来将类所拥有的域写入到对象输出流(ObjectOutputStream)中来保存起来
5.2 readObject方法
1)readObject方法用来读流,并将类的域恢复回来。
2)readObject方法可以调用in.defaultReadObject方法来执行恢复类的没有static和transient修饰的域的默认机制。这个in.defaultReadObject方法利用流中的信息来用当前对象的对应名字的域来分配存储在流中的对象的域。这解决了类在后期增加新的域的情况。
3)类的状态通过调用writeObject()方法或者用数据输出(DataOutput)所支持的针对原生(primitive)数据类型的方法来将类所拥有的域写入到对象输出流(ObjectOutputStream)中来保存起来
5.3 readObjectNoData方法:
1)readObjectNoData方法用来在序列化的流没有列出所给出的类(这个类是被反序列化的对象的一个父类)的情况下去初始化这个类的对象的域。这种情况出现在接受方用了一个和发送方不同版本反序列化实例的类,并且这个接收方的版本所继承的类并没有被发送方的版本所继承;这种情况也可能出现在序列化的流已经被篡改;
2)因此,在有一个不利的或者不完整的流的情况下,readObjectNoData方法对于去合理的初始化反序列化对象是非常有用的
6. 在将一个对象写入到流中的时候,需要指派一个可替代的对象去使用的可序列化类要去实现下面的方法:
<PRE> * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException; * </PRE>
1)如果这个方法存在,那么这个方法应该在被序列化的时候调用
2)这个方法需要能被在类的作用域下的其他方法访问,因此这个方法有public/protected/private访问权限
3)子类访问这个方法遵循java的权限访问规则
7. 当一个类的实例从流读出的时候,这个类需要指派一个可替代的对象,应该要去实现下面的方法:
<PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE>
机制和writeReplace方法类似
8.序列化版本唯一标识-SerialVersionUID
1. 定义:在序列化运行时期,会将每一个可序列化类和一个版本号关联起来,这个版本号就叫做SerialVersionUID
2. 作用:这个序列化唯一标识的作用是为了式一个可序列化的对象发送方和接收方在装载那个对象的类时相对于序列化是匹配的
3. 当接受方装载了一个对象的类和对应接收方装载的类有不同的序列化唯一标识的时候就会导致InvalidClassException
4. 一个可序列化的类可以显示声明一个SerialVersionUID,这个序列化唯一标识需要被static/final/long修饰
<PRE> * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; * </PRE>
5. 如果一个可序列化的类不显示声明SerialVersionUID,那么将会基于这个类的很多方法计算一个默认的SerialVersionuid值,但是强烈建议显示声明,为了兼容不不同的编译器;也建议SerialVersionUID被private修饰,从而不能被继承,因为继承了也没有很大意义。
6.数组不需要显式声明序列化版本唯一标识,对于数组类而言,需要接收方和发送方的序列化唯一标识是一致的已经被废弃了的。
@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 { }
二、序列化和反序列化
java对象序列化是将那些实现了Serializable接口的对象转换为一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象(反序列化)
1. 为什么需要序列化
一般创建的对象在程序关闭(JVM关闭)的时候,创建的对象就不会继续存在了;如果对象能够在程序不运行的情况下仍能存在并保存信息,那将非常有用;尽管可以通过保存到数据库或者存在文件来恢复对象;但是序列化可以将一个对象声明是“持久性”的,并为我们处理掉细节;(持久性意味着一个对象的生存周期并不取决于程序是否还在执行,它可以生存于程序的调用之间)
2. 序列化和反序列化的作用
2.1 Java的远程方法调用(RMI)
序列化和反序列化使得存活于其他计算机上的对象使用起来就像是存活于本地上一样;序列化和反序列化的过程可以通过网络进行:这意味着序列化机制能够自动弥补不同操作系统之间的差异;在window操作系统上序列化的对象可以linux操作系统上重新准确组装
当向远程对象放消息时,需要通过序列化来传输参数和返回值
2.2 对于java Beans来说,对象的序列化也是必须的;使用一个bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复。这种具体工作就是由对象序列化完成的。
3. 序列化和反序列化如何实现
3.1 序列化
要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需要调用OutputStream对象的writeObject()方法即可将对象序列化,并将其发送给OutputStream。
3.2 反序列化
要讲一个序列还原为一个对象,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject();
[注]和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们(用(ClassNname))