深入了解java序列化

时间:2022-01-21 17:34:42

在日常开发中,前端与后端的交互,系统之间的远程调用都需要使用到序列化技术,在java中使用序列化非常简单,只需要将被序列化的对象的类实现Java.io.Serializable接口即可。
对于实现序列化接口的类,我们需要注意两点:

  1. 类中的静态变量我们是无法序列化的,因为序列化只是针对对象,而静态变量是类级别的。
  2. 当子类实现序列化接口,而父类没有实现序列化接口时,将子类进行序列化,再反序列化回来后,发现父类中的属性会被重新初始化,也就是说会调用父类的无参构造。如果没有无参构造,则会抛出异常。

下面是通过两个小案例来证明:

public class SeriaTest implements Serializable{

private static final long serialVersionUID = 1L;

private static Integer count = 10;

public static void main(String[] args) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
oos.writeObject(new SeriaTest());
count = 20;
SeriaTest seriaTest = (SeriaTest) ois.readObject();
System.out.println(seriaTest.count);
} catch (Exception e) {
System.out.println("序列化反序列化时出现异常:"+e);
}
}
}

这段代码的运行结果为20,说明静态变量不会被序列化。

public class Fu {

public String fu;
public String getFu() {
return fu;
}
public void setFu(String fu) {
this.fu = fu;
}
}

public class Zi extends Fu implements Serializable{

private static final long serialVersionUID = 1L;
public String zi;

public Zi(){
fu = "fu";
zi = "zi";
}
public String getZi() {
return zi;
}
public void setZi(String zi) {
this.zi = zi;
}
}

public class SeriaTest{

public static void main(String[] args) {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))){
oos.writeObject(new Zi());
Zi zi = (Zi) ois.readObject();
System.out.println(zi.fu);
System.out.println(zi.zi);
} catch (Exception e) {
}
}
}

这段代码的运行结果为null,zi。验证了上面的第二点。

将对象序列化实际上调用的就是ObjectOutputStream的wirteObject方法,跟踪源代码可知能被序列化的对象除了是实现Serializable接口的对象,还可以是String,数组或者枚举对象:

if (paramObject instanceof String) {
writeString((String) paramObject, paramBoolean);
} else if (((Class) localObject2).isArray()) {
writeArray(paramObject, localObjectStreamClass, paramBoolean);
} else if (paramObject instanceof Enum) {
writeEnum((Enum) paramObject, localObjectStreamClass,paramBoolean);
} else if (paramObject instanceof Serializable) {
writeOrdinaryObject(paramObject, localObjectStreamClass,paramBoolean);
}

再继续跟进writeOrdinaryObject方法,我们会发现会有这么一段逻辑:

if ((paramObjectStreamClass.isExternalizable()) && (!(paramObjectStreamClass.isProxy())))
writeExternalData((Externalizable) paramObject);
else
writeSerialData(paramObject, paramObjectStreamClass);

先判断是不是实现了Externalizable接口(这个是Serializable的子接口,这里先不管,后面会解释),如果不是,则执行writeSerialData方法。跟进去:

if (localObjectStreamClass.hasWriteObjectMethod()) {
********省略*******
localObjectStreamClass.invokeWriteObject(paramObject, this);
********省略*******
}
else {
defaultWriteFields(paramObject, localObjectStreamClass);
}

我们发现源代码先判断了一下这个需要被序列化的类中有没有writeObject这个方法,如果有,那么就执行,如果没有,那么就执行默认的序列化方法。对于这个writeObject方法,有以下几个要求:

  • 方法名必须叫writeObject
  • 必须是私有的方法
  • 返回值类型必须为void

    只有满足了以上三点,在序列化对象的时候,才会执行我们自定义的序列化方法。当然,我们除了可以重写writeObject方法,我们还可以重写readObject,readObjectNoData,writeReplace,readResolve等方法,这些方法之间有什么联系,有什么作用呢?我们通过一段代码来探索一下:

public class SeriaTest implements Serializable{

private static final long serialVersionUID = 1L;

public Integer count = 10;

public SeriaTest(Integer count){
this.count = count;
}

public static void main(String[] args) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
oos.writeObject(new SeriaTest(20));
SeriaTest seriaTest = (SeriaTest) ois.readObject();
System.out.println(seriaTest.count);
} catch (Exception e) {
System.out.println("序列化反序列化时出现异常:"+e);
}
}

private void writeObject(ObjectOutputStream oos) throws IOException{
System.out.println("序列化前执行了自定义的writeObject方法");
oos.defaultWriteObject();
System.out.println("序列化后执行了自定义的writeObject方法");
}

private Object writeReplace() {
System.out.println("序列化时执行了自定义的writeReplace方法");
SeriaTest s = new SeriaTest(10);
return s;
}
}

执行结果如下:

序列化时执行了自定义的writeReplace方法
序列化前执行了自定义的writeObject方法
序列化后执行了自定义的writeObject方法
10

根据执行结果我们我们可以知道,在对象序列化的时候,会先去执行被序列化的类中的writeReplace方法,再执行writeObject方法,如果重写了writeReplace方法,那么被序列化的对象就是这个方法的返回值,而writeObject方法主要作用就是在序列化前后可以做处理操作。
对应的读操作的方法分别是readResolve方法和readObject方法,将上面的代码再完善一下:

public class SeriaTest implements Serializable{

private static final long serialVersionUID = 1L;

public Integer count = 10;

public SeriaTest(Integer count){
this.count = count;
}

public static void main(String[] args) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
oos.writeObject(new SeriaTest(20));
SeriaTest seriaTest = (SeriaTest) ois.readObject();
System.out.println(seriaTest.count);
} catch (Exception e) {
System.out.println("序列化反序列化时出现异常:"+e);
}
}

private void writeObject(ObjectOutputStream oos) throws IOException{
System.out.println("序列化前执行了自定义的writeObject方法");
oos.defaultWriteObject();
System.out.println("序列化后执行了自定义的writeObject方法");
}

private Object writeReplace() {
System.out.println("序列化时执行了自定义的writeReplace方法");
SeriaTest s = new SeriaTest(10);
return s;
}

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException{
System.out.println("反序列化前执行了自定义的readObject方法");
ois.defaultReadObject();
System.out.println("反序列化后执行了自定义的readObject方法");
}

private Object readResolve(){
System.out.println("反序列化前执行了自定义的readReslove方法");
SeriaTest s = new SeriaTest(30);
return s;
}
}

执行结果符合我们的预期:

序列化时执行了自定义的writeReplace方法
序列化前执行了自定义的writeObject方法
序列化后执行了自定义的writeObject方法
反序列化前执行了自定义的readObject方法
反序列化后执行了自定义的readObject方法
反序列化前执行了自定义的readReslove方法
30

至于readObjectNoData方法,这个比较少见,这个方法的作用就是当一个对象被序列化后,反序列化时可以添加属性。(具体可参考:readObjectNoData方法作用

下面我们再来解析一下上面提到的Externalizable接口,这个接口是Serializable接口的子接口,这个接口有两个抽象方法:writeExternal和readExternal,这个两个方法分别对应于writeObject和readObject,不同点在于writeObject和readObject都是私有方法,所以其子类不能复用并且不能复写,而writeExternal和readExternal是共有方法,其子类可以复用并且复写。

public class SeriaTest implements Externalizable{

private String name;
private Integer age;

public static void main(String[] args) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))
) {
oos.writeObject(new SeriaTest());
SeriaTest seriaTest = (SeriaTest) ois.readObject();
System.out.println(seriaTest.name);
System.out.println(seriaTest.age);
} catch (Exception e) {
System.out.println("序列化反序列化时出现异常:"+e);
}
}

@Override
public void writeExternal(ObjectOutput paramObjectOutput)
throws IOException {
System.out.println("writeExternal.....");
paramObjectOutput.writeInt(18);
paramObjectOutput.write("zhangsan".getBytes(Charset.forName("UTF-8")));
}

@Override
public void readExternal(ObjectInput paramObjectInput) throws IOException,
ClassNotFoundException {
System.out.println("readExternal.....");
age = paramObjectInput.readInt();
name = paramObjectInput.readLine();
}

}

执行结果如下:

writeExternal.....
readExternal.....
zhangsan
18