刚开始接触 Java 序列化那会儿,真没太把那个 serialVersionUID
当回事。代码里实现个 Serializable
接口,有时候 IDE 会给个黄色的警告,提示你最好加上这玩意儿。那时候年轻,觉得警告嘛又不是报错,能跑就行,或者有时候干脆就让 IDE 自动生成一个,也没细想。
后来有一次,项目不大不小,有个功能需要把一些对象状态保存到文件里,下次启动再加载回来。一开始跑得好好的,没啥问题。过一阵子,需求变点,我就去改那个需要保存的对象的类,可能就是加个不痛不痒的字段,或者改个方法的实现,自以为没影响核心数据。
结果,坏!
程序一启动,去加载之前保存的文件,直接就抛异常,说什么类版本不兼容。当时就懵,我寻思着改动不大,怎么就不能读?之前的旧数据一下子全废,用户那边要是碰上这情况,估计得骂娘。
没办法,只能硬着头皮查。折腾半天,到处看日志,搜错误信息,才把目光聚焦到那个被我忽略的 serialVersionUID
上。
搞清楚咋回事
原来是这么回事:如果你不显式地给你的可序列化类指定一个 serialVersionUID
,Java 运行时会根据这个类的结构(比如字段名、方法签名啥的)自己算一个出来。这本来也行,但问题是,只要你对这个类做任何一点改动,哪怕是加个注释或者改个变量名(有时候编译器优化也会影响),它重新计算出来的这个 ID 很可能就变。
这下好,你用新版代码(带着新的、自动生成的 ID)去读旧版代码(带着旧的、自动生成的 ID)保存的数据,两边 ID 对不上,Java 就认为这不是同一个类的兼容版本,直接拒绝加载,抛异常给你看。
我的实践
搞明白之后,我就学乖。从那以后,只要是需要序列化的类,我都会老老实实地加上这么一行:
private static final long serialVersionUID = 1L;
或者用 IDE 生成一个固定的值也行,关键是要手动指定一个,并且轻易不要改动它。
- 第一步:实现
Serializable
接口。 - 第二步:手动添加
private static final long serialVersionUID
这个静态常量。 - 第三步:给它赋一个
long
类型的值,通常写个1L
就行,或者用工具生成一个随机但固定的长整型数。
这样做之后,就算以后我对这个类做一些小的修改(比如增加字段、修改方法实现等),只要这些修改不影响到核心的、需要序列化的数据结构,我通常就不去改动这个 serialVersionUID
的值。这样一来,新版的代码就能兼容旧版保存的数据,加载的时候就不会再报那个烦人的版本不匹配错误。
如果我做的改动确实非常大,导致新旧版本完全不兼容,比如删除关键字段,或者字段类型变,那可能就得考虑升级这个 serialVersionUID
的值,但这属于特殊情况,需要明确知道自己在做什么,并且通常意味着你需要处理数据迁移的问题。
总的来说,自从养成手动指定 serialVersionUID
的习惯后,确实省不少事,尤其是在项目迭代过程中,避免很多因为类小改动导致的反序列化失败问题。这玩意儿虽然看起来不起眼,但在需要持久化对象或者网络传输对象的时候,还是挺关键的。算是踩坑之后的一个小经验。