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

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

有帮助吗?

解决方案

提供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 一次,让它创建并保存一个对象。然后更改基类中的serialVersionUID,注释掉main中保存对象的行(这样它就不会再次保存它,您只想加载旧的),然后再次运行它。这将导致异常

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

其他提示

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

如果您想做一些奇特的事情,还有其他选择。我不确定你所说的“这是子类的意图......”是什么意思。您是否要编写自定义序列化方法(例如写对象、读对象)?如果是这样,还有其他处理超类的选择。

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

汤姆·HTH

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

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

拥有不同版本的 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. 如果字段更改数据类型,则反序列化的值必须可分配给新类型。例如。如果你有一个 Object 场与 String 值,将字段类型更改为 String 会成功,但改为 Integer 惯于。然而,改变领域 intlong 不起作用,即使你可以分配 int 价值 long 多变的。
  4. 如果子类不再扩展父类(它在序列化数据中扩展了父类),则它将被忽略(如情况 1 所示)。
  5. 如果子类现在扩展了一个类,但在序列化数据中找不到该类,则父类字段将恢复为 0/false/null 值(如情况 2)。

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

笔记:如果基类没有实现 Serialized 而只有子类实现了,那么基类中的字段将表现为 transient.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top