Java序列化和反序列化

在我们实际使用中,我们经常会遇到对象需要序列化的问题。我将通过下面几个问题,理解序列化的相关问题

  1. 什么是序列化和反序列化
  2. 什么时候需要进行序列化和反序列化
  3. 实现序列化和反序列化为什么要实现Serializable接口
  4. 在实现Serializable接口时,为什么要显示指定serialVersionUID的值
  5. Java的序列化的特性
  6. static为什么不需要被序列化

什么是序列化和反序列化

  • 序列化:把Java内存对象转换为字节序列的过程称为对象的序列化
  • 反序列化:把字节序列恢复为Java内存对象的过程称为对象的反序列化

什么时候需要序列化和反序列化

当我们需要把Java的内存对象持久化到文件/数据库,或者进行网络传输时,就需要对对象进行序列化。

实现序列化和反序列化为什么要实现Serializable接口

在Java中,如果一个类实现了Serializable接口,JVM底层就会帮我们实现序列化和反序列化。如果我们不实现Serializable接口,那么就需要我们自己实现序列化和反序列化

实现Serializable接口时,为什么需要指定serialVersionUID的值

在Java中,如果一个实现了Serializable接口的类没有指定serialVersionUID,那么JVM在序列化时,会根据属性自动生成一个serialVersionUID,然后和属性一起序列化,再进行网络传输或者持久化。在反序列化时,JVM会再根据属性生成一个新版本的serialVersionUID,然后再用这个新版本和serialVersionUID和序列化时生成的旧版本的serialVersionUID进行计较。如果二者一样就可以序列化成功。反之,报错。

如果我们显示指定了serialVersionUID,JVM在序列化和反序列化时,就会使用我们指定的serialVersionUID。这样我们就可以确保在反序列化时,serialVersionUID和之前的相同。

在实际的开发中,不指定serialVersionUID的值。在我们修改一个类的属性,或者使用不同版本的jdk时,都有可能会导致自动生成serialVersionUID发生改变。从而会导致这个类在进行反序列化时失败。

下面我们写一个例子:

  1. 测试实体类,Stu,实现了Serializable接口,但是不指定serialVersionUID
public class Stu implements Serializable {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Stu{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
  1. 测试类
public class SerializableTest {
    private static void serialize(Stu stu) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./1.txt")));
        oos.writeObject(stu);
        oos.close();
    }

    private static Stu deserialize() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./1.txt")));
        return (Stu)ois.readObject();
    }

    @Test
    public void testSerializer() throws Exception {
        Stu stu = new Stu();
        stu.setId(18);
        stu.setName("hardy");
        System.out.println("before serializer" + stu);

        serialize(stu);
        Stu deserializeStu = deserialize();

        System.out.println("after deserializer:" + deserializeStu);
    }
}

此时,我们注释代码中序列化的的部分,给Stu再增加一个属性,再进行反序列化时就会报错:

java.io.InvalidClassException: com.hardy.pojo.Stu; local class incompatible: stream classdesc serialVersionUID = -5638707890362050979, local class serialVersionUID = 6871763750857709435

	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:715)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2021)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1890)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2183)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1707)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:517)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:475)
	at com.hardy.SerializableTest.deserialize(SerializableTest.java:23)
	at com.hardy.SerializableTest.testSerializer(SerializableTest.java:34)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

这是因为,序列化和反序列化的serialVersionUID不一致。我们在序列化时,增加serverVersionUID,就可以避免这个问题。

Java序列化的特性

  1. 被transient关键字修饰的属性不会被序列化
  2. static属性也不会被序列化

static属性为什么不会被实例化

因为序列化序列的时内存对象,static属性先于对象加载。随着类的加载,static就加载好了。所以不会被序列化。

serialVersionUID被static修饰,为什么会被序列化?
serialVersionUID属性并没有被序列化,JVM在序列化时会自动给对象生成一个serialVersionUID。我们手动指定的话,serialVersionUID属性的话,这个值就会赋给serialVersionUID

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值