序列化的Lambda表达式和没有serialVersionUID怎么办?

8

我想学习Java及其最新版本中的序列化工作原理。我试图像这样序列化一个lambda表达式:

Runnable r = (Runnable & Serializable)() -> {System.out.println("This is a test");};

但是我注意到关于serialVersionUID变量的缺失,我没有任何警告。这正常吗?

我知道它将在运行时生成,但强烈建议定义它:https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html

如果一个可序列化的类没有明确声明一个 serialVersionUID,则序列化运行时将根据类的各个方面计算出一个默认的 serialVersionUID 值,如Java(TM)对象序列化规范中所述。然而,强烈建议所有可序列化的类都明确声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节高度敏感,这些细节可能因编译器实现而异,因此可能导致意外的 InvalidClassExceptions 在反序列化期间。因此,为了保证在不同的java编译器实现之间具有一致的 serialVersionUID 值,可序列化的类必须声明一个显式的 serialVersionUID 值。同时,强烈建议使用 private 修饰符来声明显式的 serialVersionUID,因为这样的声明仅适用于立即声明的类—— serialVersionUID 字段作为继承成员是没有用处的。数组类不能声明显式的 serialVersionUID,因此它们始终具有默认计算值,但是对于数组类,匹配 serialVersionUID 值的要求被豁免了。

我该怎么做?如何在我的 Lambda 中定义它?

谢谢


1
似乎没有办法声明一个 serialVersionUID 字段,因为缺少定义类。这直接导致了下一个问题:为什么应该将 lambda 序列化?它没有字段,因此也没有数据! - Seelenvirtuose
1
@Seelenvirtuose 序列化的 lambda 包含在评估 lambda 时捕获的值。如果一个 lambda 使用不同的捕获值被评估多次,那么代表 lambda 的对象将不同,它们的序列化表示也将不同。 - Stuart Marks
@StuartMarks 显然我没有想得够远。谢谢你指出这一点。事实上,这帮助我改变了我的想法。 - Seelenvirtuose
1个回答

6
serialVersionUID 只与生成流标识符的类有关。如果可序列化的类有一个writeReplace()方法(也在Serializable文档中描述),返回一个不同类的替代对象,则情况并非如此,因为这样的表示与原始类完全解耦。这就是可序列化的lambda实例所发生的情况,请参见SerializedLambda

可序列化lambda的实现者(例如编译器或语言运行时库)应确保实例正确反序列化。一种方法是确保writeReplace方法返回SerializedLambda的实例,而不允许默认序列化继续进行。

因此,在流中结束的是SerializedLambda的实例,因此该类有责任具有稳定的序列化表示形式。不幸的是,这并不能保护您免受可能的不兼容性影响。

在反序列化时,将调用定义lambda表达式的类的合成方法(与thisthis answer进行比较),该方法将拒绝与该类中现有的lambda表达式定义不匹配的反序列化尝试,而匹配可能取决于lambda定义的微妙方面。请注意,即使使用Eclipse而不是javac重新编译定义类也可能会破坏序列化兼容性。

还需注意可序列化lambda的安全影响。一般建议避免使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接