抽象类是否应该具有 seralVersionUID

在 java 中,如果一个类实现了可串行化但是是抽象的,那么它应该有一个长声明的 SerialVersionUID,还是子类只需要这个?

在这种情况下,所有的子类都处理序列化,因为类型的用途是用于 RMI 调用。

20608 次浏览

提供 seralVersionUID 是为了确定反序列化对象与.因此,在类的第一个版本中,或者在这种情况下,在抽象基类中,实际上没有必要这样做。您永远不会有要序列化/反序列化的抽象类的实例,因此它不需要 seralVersionUID。类的当前版本之间的兼容性

(当然,它确实会生成一个编译器警告,您希望去掉这个警告,对吗?)

事实证明 James 的评论是正确的。抽象基类 是的的 seralVersionUID 被传播到子类。有鉴于此,需要在基类中使用 seralVersionUID。

测试代码:

import java.io.Serializable;


public abstract class Base implements Serializable {


private int x = 0;
private int y = 0;


private static final long serialVersionUID = 1L;


public String toString()
{
return "Base X: " + x + ", Base Y: " + y;
}
}






import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Sub extends Base {


private int z = 0;


private static final long serialVersionUID = 1000L;


public String toString()
{
return super.toString() + ", Sub Z: " + z;
}


public static void main(String[] args)
{
Sub s1 = new Sub();
System.out.println( s1.toString() );


// Serialize the object and save it to a file
try {
FileOutputStream fout = new FileOutputStream("object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject( s1 );
oos.close();
} catch (Exception e) {
e.printStackTrace();
}


Sub s2 = null;
// Load the file and deserialize the object
try {
FileInputStream fin = new FileInputStream("object.dat");
ObjectInputStream ois = new ObjectInputStream(fin);
s2 = (Sub) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}


System.out.println( s2.toString() );
}
}

在 Sub 中运行 main 一次,使其创建并保存对象。然后在 Base 类中更改 seralVersionUID,注释掉 main 中保存对象的行(这样它就不会再次保存对象,您只是想加载旧的对象) ,然后再次运行它。这将导致异常

java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

是的,一般来说,出于同样的原因,任何其他类都需要一个串行 ID-以避免为其生成一个。基本上,任何实现可序列化的类(非接口)都应该定义串行版本 ID,否则在相同的情况下就会出现反序列化错误。类编译不在服务器和客户端 JVM 中。

如果你想做一些花哨的事情,还有其他的选择。我不明白你说的“这是子类的意图... ...”是什么意思。是否要编写自定义序列化方法(例如 writeObject、 readObject) ?如果是这样,还有其他处理超类的选项。

见: Http://java.sun.com/javase/6/docs/api/java/io/serializable.html

汤姆

实际上,指出 Tom 的链接,如果缺少 serialVersionID实际上是由序列化运行时计算的,即不是在编译期间

如果可序列化类未显式声明 SeralVersionUID,则序列化运行库将计算一个 基于各个方面的该类的默认 seralVersionUID 值 全班同学。

这使得使用不同版本的 JRE 变得更加复杂。

从概念上讲,序列化数据如下所示:

subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)

因此,当反序列化时,层次结构中任何类的版本不匹配都会导致反序列化失败。没有为接口存储任何内容,因此不需要为它们指定版本。

所以答案是: 是的,您确实需要在基抽象类中提供 serialVersionUID,即使它没有字段: className + version仍然存储。

还要注意以下事项:

  1. 如果类没有在序列化数据(已移除的字段)中遇到的字段,则忽略该字段。
  2. 如果一个类有一个在序列化数据中不存在的字段(一个新字段) ,它被设置为0/false/null。它没有设置为预期的默认值。
  3. 如果字段更改类型,则反序列化的值必须可分配给新类型。例如,如果您有一个具有 String值的 Object字段,将字段类型更改为 String将会成功,但将其更改为 Integer则不会成功。但是,将字段从 int更改为 long将不起作用,即使您可以将 int值赋给 long变量。
  4. 如果一个子类不再扩展它在序列化数据中扩展的父类,它将被忽略(如情况1)。
  5. 如果一个子类现在扩展了一个在序列化数据中没有找到的类,那么父类字段将用0/false/null 值还原(如情况2)。

简单来说: 您可以重新排序字段,添加和删除它们,甚至更改类层次结构。您不应该重命名字段或类(它不会失败,但是它会被处理,就好像那个字段被删除和添加了一样)。不能使用基元类型更改字段的类型,并且可以更改引用类型字段,前提是新类型可从所有序列化值中赋值。

注意: 如果基类没有实现 Serializable,而只有子类实现了,那么基类中的字段将表现为 transient