浅谈一下Java的Serializable 接口(序列化与反序列化)

最浅层次的认知:要序列化的类实现 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,而不是序列化前的状态“美丽的”。

为什么呢?transient 的中文字义为“临时的”(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null。
  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值