Java序列化就是将Java对象转换成字节流,可用于持久化存储,或者网络传输等。反序列化就是将字节流再转成对象。
1、序列化使用
在Java中,类实现了java.io.Serializable接口就可以被序列化。Java的枚举默认继承类java.lang.Enum也实现了Serializable接口,所以枚举类型对象都是默认可序列化的。另外数组也是可以序列化的。
那通过实现Serializable的类如何进行序列化和反序列化呢。
下面是一个简单的序列化代码,将person对象序列化后写入到file文件中。
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Persion persion = new Persion();
oout.writeObject(person);
oout.close();
在反序列化过程中,重新读取对象时,没有调用构造器,直接能将对象还原。(其中读取程序要确保ClassPath中包含读取的类Class,否则抛出ClassNotFoundException。)
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject();
Oin.close();
2、序列化机制
默认序列化
只实现了Serializable接口的话,则使用默认机制,不仅序列化该对象本身,还会序列化引用的其他对象。同样,这些引用的对象引用的其他对象也会被序列化。
如一个成员变量是容器,这个成员变量也会被序列化(很多集合类都实现了Serializable接口),容器中包含的元素也会被序列化。这样过程就很复杂,开销也很大。
影响序列化
有的时候不想使用默认的序列化机制,如有些字段想忽略,有些数据想加密等,有下面几种影响序列化的方式:
- transient关键字
被声明为transient的字段,在默认序列化中会被忽略。
transient private String name;
- writeObject()与readObject()方法
被序列化的对象中添加writeObject(ObjectOutputStream out)与readObject(ObjectInputStream in)方法(都是private void),可以覆盖序列化和反序列化过程。
transient private String name;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();//默认序列化机制
out.writeString(name);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();//默认序列化机制
name = in.readString();
}
- Externalizable接口
继承Externalizable接口则基于Serializable机制失效,该接口需要实现writeExternal(ObjectOutput out)与readExternal(ObjectInput in)方法,在这两个方法中实现序列化的过程。
public class Person implements Externalizable {
private ind id;
transient private String name;
public Person() {
System.out.println("反序列化readExternal时调用该构造函数");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readInt();
name = (String) in.readObject();
}
}
使用Externalizable序列化过程,读取对象时会调用类的无参构造器来创建一个对象,然后再调用readExternal(),可以在方法中用被保存的属性值填充到新对象中。
- readResolve()方法
如果类实现了Object readResolve()方法,则不管Serializable还是Externalizable接口都失效,读取对象时获得的是该方法返回的对象。
序列化版本
Java反序列化对象时,需要这个对象类的class文件来感知类的结构。这个类结构是有可能变化的,如增加修改字段等,所以我们一般会对需要序列化反序列化的类中定义serialVersionUID
private static final long serialVersionUID
这个serialVersionUID就是用来版本控制的。类升级会向下兼容的话,这个serialVersionUID就不需要修改,但如果不兼容的话,可以修改serialVersionUID值。如果需要反序列化的对象中serialVersionUID与当前引用的class文件中的serialVersionUID不一致的话,会报版本不一致的错误。
如果不定义这个serialVersionUID,Java会自动生成这个值,而修改类文件重新编译后,这个类再生成的serialVersionUID值可能会发生变化,这时就会导致两个serialVersionUID版本不一致而反序列化失败。