Serializable接口的作用性质

来源:互联网 发布:linux拷贝当前目录 编辑:程序博客网 时间:2024/05/15 13:46

提到Serializable,自然就会想到序列化,那么这里先说下序列化的作用。

序列化是java持久化保存对象数据的一个过程,反序列化也就是将保存的对象数据恢复的过程。序列化主要作用包括:

1、可永久保存对象状态

2、将对象数据转为字节,方便网络传输,如socket通讯,分布式应用等

3、序列化后需进行对应的反序列化恢复,所以具有更好的安全性。

虽然字面上很简单,实际使用中有许多值得注意的地方。

在实际应用中,java的api里面就有很多类型实现Serializable接口,而项目框架里面,通常会要求限定一些需要持久化操作的类型实现Serializable接口,如hibernate,mybatis等。一个很常见的场景:

 * @param <E> 用于映射的持久化类 * @param <PK> 用于查询的键 */  public interface GeneralInterface <E extends Serializable,PK extends Serializable>{E load(PK id);E loadLazy(PK id);List<E> loadList(String key);}
这要求操作提供的类型或键需要序列化,这会带来一些好处,例如用于缓存框架是必须的,如mybatis的自定义缓存,readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。


Serializable是一个空接口,它的实现类无需重写任何方法,只要实现了该接口,即相当于开启可序列化操作标识。否则不能进行序列化。在api中所述:可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。Serializable有一个子接口Externalizable,这个在后面会说到。


并不是只要实现了Serializable接口,该类就可以进行序列化,如果它没有一个无参的可访问的构造方法,否则不能进行序列化。如果一个不能序列化的类(没有实现Serializable接口),想要保存它的状态,可以用一个子类并且该子类必须有一个无参的可访问的构造方法。


遇到不可序列化的类,会抛出NotSerializableException异常,序列化和反序列化经常离不开ObjectOutputStream和ObjectIntputStream,还有transient,可阻止某些字段被序列化,当然,你还可以额外的序列化一些字段属性,这就会要求序列化的类再实现这三个方法:

private void writeObject(java.io.ObjectOutputStream out)     throws IOException private void readObject(java.io.ObjectInputStream in)     throws IOException, ClassNotFoundException; private void readObjectNoData()      throws ObjectStreamException;

这样就可以在序列化和反序列化中进行特殊处理。这些方法都不受该类的父类或子类的状态的影响,例如标记了transient的字段,都可以在这些方法里面无视。特别对于readObjectNoData方法,可以无视持久化对象是否被篡改、版本不一致,都会进行恢复。

还有两个方法:

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

可以使用准确的签名来实现用其他类代替当前序列化的对象。用来代替的类同样要求满足可序列化。

serialVersionUID是一个版本号,用来辨别序列化前的对象和反序列化的对象的版本是否匹配或兼容。如果不兼容则会抛出InvalidClassException异常。通常建议设置为private static final常量,如一个类实现Serializable,需要设置:


1、private static final long serialVersionUID = -4628625523610863845L;
2、private static final long serialVersionUID = 1L;


并且,尽可能显式的声明(也就是定义出来),如上面的方式,如果没有必要,不要随意修改版本号。引api中所述:强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。


上面的第一条方式是由编译器根据类的结构计算的哈希值,第二条是人工给定。你也许发现实际中人工给定并不能设为0L,那是因为不可序列化类默认就是0L,动态代理类和枚举类也是始终是0L,因为它们的机制有所不同。特别的,对于数组类不需要设定serialVersionUID,因为数组总是使用一个不变的机器的serialVersionUID默认值。说到serialVersionUID,根据什么产生,受到那些条件(如修饰符,内容)影响,又是一个很大的探讨话题,这里就不细说了。


Externalizable实际上相当于一个可控的自定义Serializable,该子接口要求实现两个方法:

@Overridepublic void writeExternal(ObjectOutput out) throws IOException {// TODO Auto-generated method stub}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {// TODO Auto-generated method stub}
这两个方法中可以通过调用ObjectOutput、ObjectIntput,以及DataInput中的方法来自行实现序列化和反序列化组合操作。

如果对象实现了Externalizable,则会使用上述方法实现序列化,如果实现的是Serializable,则会是通过ObjectOutputStream来序列化,如果即实现了Externalizable又实现了Externalizable,那么会优先使用Externalizable的方法代替Serializable的方法,如writerObject。




原创粉丝点击