Java标识接口Serializbale

    对于Java序列化的认知,我一直停留在最浅显的认知上(把那个要序列化的类实现 Serializbale接口就可以了)。但是我工作也将近有两年了,见到Serializbale 的次数越来越多,我便对它产生了浓厚的兴趣。最近也是因为疫情的原因,刚刚复工没多久,工作不饱和,有点时间研究研究了。

关于Java序列化:

Java 序列化是早在JDK 1.1 时就已经引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。

序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

 

查看源码,发现Serializbale接口里边实际什么都没有。但是关于他的注释描述确是一大堆。大致描述的内容就是类序列化的实现方式、不支持序列化时会出现的问题等等,足见这个接口的重要性。

来个例子:

首先创建一个Person类,只有name和age两个字段,不实现Serializbale接口。

 

再创建一个测试类,通过 ObjectOutputStream 将Person写入到文件当中,这就是一种序列化的过程;

再通过 ObjectInputStream 将Person从文件中读出来,这就是一种反序列化的过程。

 

由于Person没有实现Serializbale,运行时会报错:

 

顺着报错的堆栈信息,找到ObjectOutputStream的writeObject0() 方法。部分源码如下:

 

可以看出ObjectOutputStream在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException。

而如果Person实现了Serializable就可以序列化成功了。

 

以ObjectOutputStream为例,其一个对象序列化的时候会依次调用:writeObject()writeObject0()writeOrdinaryObject()writeSerialData()invokeWriteObject()defaultWriteFields()

以ObjectInputStream为例,其一个对象序列化的时候会依次调用: readObject()readObject0()readOrdinaryObject()readSerialData()defaultReadFields()

到这里其实能看出:Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

特殊的static 和 transient:

static  transient 修饰的字段是不会被序列化的。

如下面的例子

 

 

 

所以可以看出:

  1. 被static修饰的字段不会被序列化
  2. 被transient修饰的,反序列化后的值为null,而不是序列化前的值。

因为序列化保存的是对象的状态,static 修饰的字段属于类的状态,因此可以证明序列化并不保存 static 修饰的字段。

而transient 的中文字义为“临时的”(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null。

serialVersionUID:

serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。

所以建议当一个类实现了 Serializable 接口后,该类最好产生一个序列化 ID,就像下面这样:

附上idea设置提醒产生一个序列化 ID:

我们来验证一下这个问题:

思路大致是这样的:先序列化一个对象,然后改变serialVersionUID,然后再反序列化。看看结果

序列化:

改变serialVersionUID:

 

 

反序列化:

结果:InvalidClassException

 

总结:

小小的Serializable 竟然有这么多可以研究的内容!

今后如果再遇到序列化问题以及InvalidClassException异常,我想应该能够明白问题的原因了。