【问题标题】:Should an abstract class have a serialVersionUID抽象类是否应该有一个 serialVersionUID
【发布时间】:2010-10-27 22:53:49
【问题描述】:

在java中,如果一个类实现了Serializable但是是抽象的,它应该有一个serialVersionUID long声明,还是子类只需要这个?

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

【问题讨论】:

  • 我一直在写答案,然后意识到我不太清楚,尽管我有预感。为我无法回答的问题 +1。

标签: java serialization abstract-class serialversionuid


【解决方案1】:

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:谢谢,但我通常将警告视为错误并直接处理它们。
  • ...但我知道并不是每个人都像我一样教条主义,所以你的评论很受欢迎。
  • 其实这是不正确的。在反序列化过程中,继承链中所有类的 serialversionuid 都被考虑在内,因此在抽象类上缺少一个可能是有问题的。我实际上遇到了这个问题。
  • 它也应该出现在类的第一个版本中,因为用不同的编译器重新编译它可能会产生不同的默认 serialVersionUID。从而呈现与旧版本不兼容的类的新编译版本(没有代码更改)。查看注释java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/…
【解决方案2】:

是的,一般来说,出于同样的原因,任何其他类都需要序列号 - 以避免为其生成序列号。基本上任何实现可序列化的类(不是接口)都应该定义序列版本 ID,否则当相同的 .class 编译不在服务器和客户端 JVM 中时,您将面临反序列化错误的风险。

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

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

HTH 汤姆

【讨论】:

    【解决方案3】:

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

    如果一个可序列化的类没有显式声明一个 serialVersionUID,那么序列化运行时会计算一个 基于各个方面的该类的默认 serialVersionUID 值 班级的...

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

    【讨论】:

      【解决方案4】:

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

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-06
        • 2013-03-06
        • 1970-01-01
        • 2013-08-23
        • 2022-10-07
        相关资源
        最近更新 更多