在一次代码审查中,我看到了这样的代码。。。
public class IntersectionException extends Exception
static final long serialVersionUID = 42L;
正如我所知,我们需要实现serialVersionUID,以确保反序列化与序列化相同版本的Object。如果我们看看类Exception的层次结构,我们会发现有一个"异常"的实现;可串行化的";界面
但是在类Exception中,我们已经有了serialVersionUID。所以,我的问题是,我是否也应该在Exception的子类中声明serialVersionUID?
这是一个愚蠢的linting规则,所以,不,你不应该这样做。
具体地说,规则是:;所有可序列化的东西都必须具有serialVersionUID";非常可疑。将此规则应用于偶然可序列化的事物,例如所有异常子类型,是完全愚蠢的。
坚持linting规则要么[A]直接让你的代码变得更糟(是的,这个linting规则会激励你让代码变得更糟糕。我用"愚蠢"这个词是有充分理由的),要么[B]只会让它变得更糟,但像这样"半正确"地写它实际上真的很昂贵,因为它需要大量的时间来做正确的事情,然后你的代码仍然比你忽略它更糟糕。
我最好支持这些激烈的主张!
什么是serialVersionUID
每当使用java的内置序列化机制序列化任何对象时,类的"串行版本ID"也会写入数据中。反序列化时,该类需要位于类路径上,AND必须具有相同的svUid。如果没有,则反序列化将引发异常。任何类的svUid定义为:
- 如果存在CCD_ 2字段的值
或者如果不是,
- 计算它的结果,它基于对该类的所有签名方面进行哈希(扩展、实现、所有字段的类型和名称、返回类型、名称以及所有方法的参数类型和名称)。如果您想查看它,
serialver
工具(在任何JDK发行版的bin
目录中)会告诉您
为什么这个linter规则不好
它之所以愚蠢地抱怨这一点,是因为序列化机制的UID部分就是它本身,虽然它不太好,但实际上它比这个linting规则所要求的更好。具体来说,这是串行版本UID管理的正确模型1:
- 所有类都以计算的svUid开始不要显式添加一个
- 当你更新代码时,你实际上正在做一个愚蠢的举动,即使用序列化在同一应用程序的不同版本之间进行通信,那么默认情况下,如果任何签名的任何部分发生了更改,任何类都不再与以前的版本兼容
- 。。。除非作者明确选择编写与旧版本兼容的版本更新,并花时间编写测试和记录这种行为。在这种情况下,他们显然有旧版本(否则他们怎么能承诺,如果你甚至没有旧版本,你可以使用新版本反序列化用旧版本"保存"的东西?)-所以他们在旧版本上运行
serialver
工具,显式地将其写入新版本
上面的方法显然(无论如何对我来说)远远优于任何其他方法,如果(去)在版本之间序列化是相关的(这真的不应该)。因此,如果您遵循"根本不使用java的序列化机制"的规则,那么linting规则应该被禁用。如果你采用另一种最好的方法,linting规则仍然应该被禁用:事实上,所有类一开始都没有任何svUid,这是正确的。您的linting工具不知道这是需要与版本1串行兼容的版本2,因此不可能存在只在这种(极为罕见)情况下触发的linting规则。因此,短绒规则不好,请禁用它
我不能,我必须加一个
好吧,让我们忽略所有这些,并说我们必须为它添加一个值。但我们应该输入什么数字?有3种选择。
常数值,例如1L
这只是改变了约定:不是默认为"任何签名更新都会使类与以前版本的类不兼容-要更改此行为,请添加svUid",而是现在将此行为更改为"任何特征码更改,即使添加字段,也无关紧要-该类仍然与以前版本兼容-要改变此行为,则添加svUid"。
显然,这是一个糟糕的违约。但这是一个简单的方法,可以进一步火上浇油:如果你使用了一些明显的常数(1、42、31等等),门楣应该发出警告。
实际svUid
与中一样,serialver
将生成什么。这是多余的,因此违反了DRY。这也是一项艰巨的工作:编写一个dummy,编译它,运行serialver
,复制它。
你想要什么:上一版本的svUid,IF你与它兼容
这才是真正的答案。不过,门楣工具并不能帮到你。
注意:这个答案是对lombok项目的功能请求的回复的一个稍微编辑过的副本,该请求要求您自己的异常子类型的serialVersionUID。参见lombok项目第3016期。
[1] 实际上,进行序列化的最好方法是根本不使用java的内置机制,而是使用经过适当考虑的库。