实际开发中使用 Serializable 接口会遇到的问题

本文深入探讨了Java序列化过程中遇到的问题,包括非序列化成员变量导致的异常、静态变量不可序列化的原理、多引用写入的注意事项以及解决办法。同时,介绍了子类在父类未实现序列化时如何进行序列化操作,并讲解了序列化时字段不匹配的情况以及如何处理。此外,还讨论了单例模式在序列化下的挑战及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类实现了序列化接口,但是存在没有实现序列化接口的成员,运行报错:java.io.NotSerializableException

需要使用 transient 关键字修饰没有实现序列化接口的成员。
值得注意的是,如果没有实现序列化接口的成员变量的值是 null,那么即便不加 transient 关键字也不会报错。

静态变量为什么无法序列化?

静态变量不参与序列化,序列化的是对象的成员字段。

多引用写入问题:同一个引用,多次写入不同的对象内容,但取出的对象是一模一样的 演示代码:

// 序列化
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person2 personWrite = new Person2("wzc", 32);
oos.writeObject(personWrite);
personWrite.setAge(33);
oos.writeObject(personWrite);
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Person2 personRead1 = (Person2) ois.readObject();
Person2 personRead2 = (Person2) ois.readObject();
ois.close();
System.out.println("personWrite:" + personWrite);
System.out.println("personRead1:" + personRead1);
System.out.println("personRead2:" + personRead2);
System.out.println("personRead1 == personRead2:" + (personRead1 == personRead2));
打印信息:

personWrite:Person2@692404036{name='wzc', age=33}
personRead1:Person2@1072408673{name='wzc', age=32}
personRead2:Person2@1072408673{name='wzc', age=32}
personRead1 == personRead2:true

第一次使用 personWrite 写入的对象内容是 “wzc”, 32;
第二次使用 personWrite 写入的对象内容是“wzc”, 33;
但是,反序列化读取到的是一模一样的对象。

解决办法:

  • 在第二次写入之前增加代码 oos.reset();
  • 把第二次写入的代码:oos.writeObject(personWrite); 替换为 oos.writeUnshared(personWrite);
  • 尽量避免多引用写入,使用不同的引用。

父类实现了Serializable,子类没有, 子类是否可以进行序列化?

可以。

子类实现序列化,父类不实现序列化,如何序列化父类的数据?

首先,要给父类添加空参构造器,否则会报错:java.io.InvalidClassException: com.java.advanced.features.io.serialize.serializable.Man; no valid constructor;
其次,让子类负责序列化(反序列化)父类的域。
代码如下:

public class Person7 {
    public String name;
    public int age;
    // 添加了无参构造器
    public Person7() {
    }
    public Person7(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Man3 extends Person7 implements Serializable  {
    public double salary;

    public Man3(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        // 先序列化本类对象
        oos.defaultWriteObject();
        // 再序列化父类的域
        oos.writeObject(name);
        oos.writeInt(age);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 先反序列化本类对象
        ois.defaultReadObject();;
        // 再反序列化父类的域
        name = (String) ois.readObject();
        age = ois.readInt();
    }
}

序列化的时候多一个字段,反序列化的时候少一个字段,或者序列化的时候少一个字段,反序列化的时候多一个字段,会不会报错?

需要显式地声明 serialVersionUID 的值,如为 1L。

private static final long serialVersionUID = 1L;
因为计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。

如果类的 serialVersionUID 是一致的,即便在序列化时的类和反序列化时的类有些不同,该对象仍会被尽最大限度完成反序列化。

writeReplace,writeObject, readResolve,readObject 的执行顺序
writeReplace 先于writeObject, readResolve后于readObject

反序列化打破单例,如何解决?

给单例添加 readResovle() 方法:

public class SingletonSerializeFix implements Serializable {
    private static final long serialVersionUID = 1L;

    private SingletonSerializeFix() {
        //no instance
    }

    public static SingletonSerializeFix getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static SingletonSerializeFix instance = new SingletonSerializeFix();
    }

    private Object readResolve() {
        return SingletonHolder.instance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值