最浅层次的认知:要序列化的类实现 Serializbale 接口
本来我对Serializable 接口也是会用即可,但是随着学习的深入,见到它的次数越来越多,然后写一篇文章记录一下~
1. 先谈谈理论
Java 序列化是在 JDK 1.1 中引入的开创性的特性。
- 目的:将 Java 对象转换为字节数组,便于存储或传输。 并且可以将字节数组转换回 Java
- 对象原有的状态(反序列化)
序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输
反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象
序列化有一条规则,就是要序列化的对象必须实现 Serializbale 接口,
否则就会报 NotSerializableException 异常。
public interface Serializable { //Serializbale 接口的定义
}
哦吼!就是一个空接口嘛!!他怎么能保证实现Java对象的序列化和反序列化呢!!
(这时候,了解他的兴趣就来了!)
2. 做一个小实战
创建一个Wanger 类(添加对应的 getter/setter),用于序列化和反序列化。
class Wanger {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
创建一个测试类,通过 ObjectOutputStream 将“16岁的小红”写入到文件当中,实际上就是一种序列化的过程;
再通过 ObjectInputStream 将“16岁的小红”从文件中读出来,实际上就是一种反序列化的过程。
// 初始化
Wanger wanger = new Wanger();
wanger.setName("小红");
wanger.setAge(16);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
此时!!! 由于 Wanger 没有实现 Serializbale 接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:
java.io.NotSerializableException: com.Wanger
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.Test.main(Test.java:21)
顺着堆栈信息,我们来看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源码如下:
// 判断对象是否为字符串类型,如果是,则调用 writeString 方法进行序列化
if (obj instanceof String) {
writeString((String) obj, unshared);
}
// 判断对象是否为数组类型,如果是,则调用 writeArray 方法进行序列化
else if (cl.isArray()) {
writeArray(obj, desc, unshared);
}
// 判断对象是否为枚举类型,如果是,则调用 writeEnum 方法进行序列化
else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
}
// 判断对象是否为可序列化类型,如果是,则调用 writeOrdinaryObject 方法进行序列化
else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
}
// 如果对象不能被序列化,则抛出 NotSerializableException 异常
else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
也就是说,ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException。
那让Wanger 实现一下 Serializable 接口吧
class Wanger implements Serializable{
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
}
序列化过程:
以 ObjectOutputStream 为例吧,它在序列化的时候会依次调用 writeObject()→writeObject0()→writeOrdinaryObject()→writeSerialData()→invokeWriteObject()→defaultWriteFields()
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
// 获取对象的类,并检查是否可以进行默认的序列化
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
// 获取对象的基本类型字段的数量,以及这些字段的值
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
// 将基本类型字段的值写入输出流
bout.write(primVals, 0, primDataSize, false);
// 获取对象的非基本类型字段的值
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
// 循环写入对象的非基本类型字段的值
for (int i = 0; i < objVals.length; i++) {
// 调用 writeObject0 方法将对象的非基本类型字段序列化写入输出流
try {
writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
}
// 如果在写入过程中出现异常,则将异常包装成 IOException 抛出
catch (IOException ex) {
if (abortIOException == null) {
abortIOException = ex;
}
}
}
}
反序列化流程:
以 ObjectInputStream 为例,它在反序列化的时候会依次调用 readObject()→readObject0()→readOrdinaryObject()→readSerialData()→defaultReadFields()
private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOException {
// 获取对象的类,并检查对象是否属于该类
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
// 获取对象的基本类型字段的数量和值
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 从输入流中读取基本类型字段的值,并存储在 primVals 数组中
bin.readFully(primVals, 0, primDataSize, false);
if (obj != null) {
// 将 primVals 数组中的基本类型字段的值设置到对象的相应字段中
desc.setPrimFieldValues(obj, primVals);
}
// 获取对象的非基本类型字段的数量和值
int objHandle = passHandle;
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
// 循环读取对象的非基本类型字段的值
for (int i = 0; i < objVals.length; i++) {
// 调用 readObject0 方法读取对象的非基本类型字段的值
ObjectStreamField f = fields[numPrimFields + i];
objVals[i] = readObject0(Object.class, f.isUnshared());
// 如果该字段是一个引用字段,则将其标记为依赖该对象
if (f.getField() != null) {
handles.markDependency(objHandle, passHandle);
}
}
if (obj != null) {
// 将 objVals 数组中的非基本类型字段的值设置到对象的相应字段中
desc.setObjFieldValues(obj, objVals);
}
passHandle = objHandle;
}
搜奈斯! Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用!!!告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成!!!!
所以Serializable 接口就相当于序列化的**钥匙**! 打开序列化的大门!!!!
了解到这里,小伙伴应该对Serializable 接口 掌握了~~~
3. 小小注意点
并不是所有的属性都可以被序列化的!!!!
static 和 transient 修饰的字段是不会被序列化的。
我们来证明一下:
在 Wanger 类中再增加两个字段
class Wanger implements Serializable {
private String name;
private int age;
public static String pre = "漂亮的";
transient String meizi = "美丽的";
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";
}
}
在测试类中打印序列化前和反序列化后的对象,并在序列化后和反序列化前改变 static 字段的值。具体代码如下:
// 初始化
Wanger wanger = new Wanger();
wanger.setName("小红");
wanger.setAge(16);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hong"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 改变 static 字段的值
Wanger.pre ="不漂亮的";
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("hong")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
输出结果:
Wanger{name=小红,age=16,pre=漂亮的,meizi=美丽的}
Wanger{name=小红,age=16,pre=不漂亮的,meizi=null}
从结果的对比当中,我们可以发现:
1)序列化前,pre 的值为“漂亮的”,序列化后,pre 的值修改为“不漂亮的”,反序列化后,pre 的值为“不漂亮的”,而不是序列化前的状态“漂亮的”。
为什么呢?因为序列化保存的是对象的状态,而 static 修饰的字段属于类的状态,因此可以证明序列化并不保存 static 修饰的字段。
2)序列化前,meizi 的值为“美丽的”,反序列化后,meizi 的值为 null,而不是序列化前的状态“美丽的”。