怎么让一个 Java 对象变得可序列化?
要让一个Java对象可序列化,非常简单,只需要满足以下几个条件
1. 实现java.io.Serializable接口
这是一个标记接口(Marker Interface),里面没有任何方法需要实现。它的作用只是告诉JVM:这个类的对象是可以被序列化的。
2. 核心注意事项:
- 成员变量也必须是可序列化的:如果
User类中包含另一个自定义类Address的引用,那么Address类也必须实现Serializable接口,否则在序列化时会抛出NotSerializableException。 transient关键字:如果某些敏感字段(如密码)或不需要保存的字段(如临时计算结果)不需要参与序列化,可以使用transient关键字修饰。- 静态变量(
static)不会被序列化:因为静态变量属于类,而不属于具体的对象实例。 - 父类的序列化:如果一个子类实现了
Serializable,而父类没有,子类仍然可以序列化,但反序列化时,父类的无参构造函数会被调用以初始化父类的属性。因此,父类必须有一个无参构造函数
2.serialVersionUID 的作用是什么
serialVersionUID 是 Java 序列化机制中的一个版本控制号。它是一个 private static final long 类型的常量。它的核心作用是:确保序列化和反序列化过程中,类结构的一致性(兼容性)。在反序列化时,JVM 会把传进来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行对比:
• 如果相同,说明两者的版本一致,可以成功进行反序列化。
• 如果不同,JVM 就会抛出 InvalidClassException 异常,拒绝反序列化。
应用场景:
当你把一个对象序列化保存到了硬盘上,过了一段时间,你修改了该类的代码(比如增加了一个新字段)。
• 如果你定义了固定的 serialVersionUID,Java 会尽最大努力帮你兼容(新字段会赋予默认值,旧字段正常恢复)。
• 如果你没有定义,或者改变了 serialVersionUID,Java 就会认为这是两个完全不同的类,直接报错。
3.如果不显式定义serialVersionUID会发生什么?
如果你在类中没有显式声明serialVersionUID,Java 运行时环境(JVM)会自动根据这个类的结构(类名、实现的接口、属性、方法等)计算出一个哈希值作为默认的serialVersionUID。
这会带来以下严重的问题和隐患:
1. 极其容易导致InvalidClassException崩溃
只要你的类发生了一丁点修改(哪怕只是加了一个空格、修改了一个不相干的方法、或者改了一个属性的修饰符),编译器重新编译后计算出的默认serialVersionUID就会发生改变。
- 后果:之前序列化保存到磁盘上的数据,在类修改后,将永远无法被反序列化回来。
2. 平台和编译器移植性差(最致命的问题)
不同厂商的 JVM(如 Oracle HotSpot、IBM J9、OpenJDK)或者不同的编译器,其计算serialVersionUID的算法可能会有细微差别。
- 后果:你在 A 机器(Windows 平台,使用 Oracle JDK)上序列化的对象,拿到 B 机器(Linux 平台,使用 OpenJDK)上反序列化时,即使类的源码完全一模一样,也可能会因为计算出的默认
serialVersionUID不同而导致反序列化失败。想·
4.最佳实践
- 如果你做的是兼容性修改(如新增一个字段),不要修改
serialVersionUID。 - 如果你做的是非兼容性修改(如修改了字段类型,或者删除了核心字段),需要手动修改
serialVersionUID,以防止旧数据反序列化时出现逻辑错误。