抽象类是否应该具有serialVersionUID


68

在Java中,如果一个类实现了Serializable但是抽象的,则是否应该声明一个long long serialVersionUID,或者子类仅要求这样做?

在这种情况下,确实确实希望所有子类都处理序列化,因为要在RMI调用中使用类型的目的。


3
我开始写一个答案,然后意识到我不太清楚,尽管我有预感。+1我无法回答的问题。
迈克尔·迈尔斯

Answers:


51

提供serialVersionUID来确定反序列化对象与类的当前版本之间的兼容性。 这样,在类的第一个版本中,或者在这种情况下,在抽象基类中,就没有必要。您永远不会有该抽象类的实例进行序列化/反序列化,因此它不需要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() );
    }
}

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

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

好的答案... @SuppressWarnings(“ serial”)将禁止显示警告消息
Ryan Anderson

1
@Ryan:谢谢,但是我通常将警告视为错误并直接处理。
比尔蜥蜴,

1
...但是我知道,并不是每个人都像我一样信奉教条,因此感谢您的评论。
比尔蜥蜴

33
实际上,这是不正确的。在反序列化期间,考虑了继承链中所有类的serialversionuid,因此抽象类上缺少一个可能会带来问题。我实际上遇到了这个问题。
詹姆斯

2
它也应该出现在该类的第一个版本中,因为使用其他编译器重新编译它可能会产生不同的默认serialVersionUID。因此,该类的新编译版本(无代码更改)与旧版本不兼容。检查笔记java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/...
罗宾

7

是的,一般而言,出于任何其他类都需要序列号的相同原因-避免为其生成一个序列号。基本上,实现可序列化的任何类(非接口)都应定义序列号ID,否则当服务器和客户端JVM中没有相同的.class编译时,您可能会面临反序列化错误。

如果您想做一些别的事情,还有其他选择。我不确定“这是子类的意图...”的意思。您要编写自定义序列化方法(例如writeObject,readObject)吗?如果是这样,还有其他选择可以处理超类。

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

汤姆(HTH)


3

实际上,指出Tom的链接是否丢失serialVersionID是由序列化运行时实际计算的,即不是在编译过程中计算的

如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面来计算该类的默认serialVersionUID值。

使用不同版本的JRE,事情变得更加复杂。


1

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

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. 如果字段更改类型,则反序列化的值必须可分配给新类型。例如,如果您有一个Object带有String值的字段,String则将字段类型更改为将成功,但将其更改为Integer不会。然而,从不断变化的领域int,以long将不起作用,即使您可以指定intlong变量。
  4. 如果子类不再扩展其在序列化数据中扩展的父类,则将其忽略(与情况1相同)。
  5. 如果现在子类扩展了在序列化数据中找不到的类,则将使用0 / false / null值还原父类字段(与情况2相同)。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.