抽象类是否应该具有serialVersionUID

Yis*_*hai 62 java serialization abstract-class serialversionuid

在java中,如果一个类实现Serializable但是是抽象的,它是否应该声明一个longVersionUID,或者子类只需要它?

在这种情况下,确实意图是所有子类都处理序列化,因为该类型的目的是在RMI调用中使用.

Bil*_*ard 46

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

(当然,它会生成一个编译器警告,你想摆脱它,对吧?)

事实证明詹姆斯的评论是正确的.抽象基类的serialVersionUID 确实传播到子类.在光,你需要在基类中的serialVersionUID.

要测试的代码:

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() );
    }
}
Run Code Online (Sandbox Code Playgroud)

在Sub中运行main以使其创建并保存对象.然后更改Base类中的serialVersionUID,注释掉main中保存对象的行(因此它不再保存它,你只想加载旧的),然后再次运行它.这将导致异常

java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
Run Code Online (Sandbox Code Playgroud)

  • 实际上,这是不正确的.在反序列化期间,会考虑继承链中所有类的serialversionuid,因此在抽象类中缺少一个类可能会有问题.我实际上遇到了这个问题. (32认同)
  • 它也应该出现在类的第一个版本中,因为使用不同的编译器重新编译它可能会产生不同的默认serialVersionUID.因此,渲染类的新编译版本(没有代码更改)与旧版本不兼容.查看说明http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/class.html#4100 (2认同)

Tom*_*Tom 6

是的,通常,出于同样的原因,任何其他类都需要一个序列ID - 以避免为它生成一个.基本上,任何实现serializable的类(不是接口)都应该定义串行版本ID,否则当同一个.class编译不在服务器和客户端JVM中时,就会出现反序列化错误.

如果你想做一些奇特的事,还有其他选择.我不确定你的意思是"这是子类的目的......".你打算编写自定义序列化方法(例如writeObject,readObject)吗?如果是这样,还有其他选择来处理超类.

请参阅:http: //java.sun.com/javase/6/docs/api/java/io/Serializable.html

HTH Tom


Oli*_*liv 5

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

subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)
Run Code Online (Sandbox Code Playgroud)

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

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

另请注意以下事项:

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

简而言之:您可以重新排序字段、添加和删除它们,甚至更改类层次结构。您不应该重命名字段或类(它不会失败,但会像删除并添加该字段一样进行处理)。您无法更改原始类型的字段类型,并且可以更改引用类型字段,前提是新类型可从所有序列化值分配。

注意:如果基类未实现Serializable而只有子类实现,则基类中的字段将表现为transient.