代码之家  ›  专栏  ›  技术社区  ›  Yishai

抽象类是否应该具有serialVersionUID

  •  60
  • Yishai  · 技术社区  · 17 年前

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

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

    4 回复  |  直到 17 年前
        1
  •  52
  •   Bill the Lizard    17 年前

    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() );
        }
    }
    

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

    java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    
        3
  •  3
  •   Viktor Stolbin    12 年前

    事实上,如果汤姆的链接丢失了 serialVersionID 实际上是由序列化运行时计算的,即不是在编译期间

    如果可序列化类未显式声明 班上的。。。

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

        4
  •  2
  •   Oliv    6 年前

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

    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 值,将字段类型更改为 一串 将成功,但将其更改为 Integer int long 不起作用,即使您可以分配 长的
    4. 如果子类不再扩展它在序列化数据中扩展的父类,它将被忽略(如案例1所示)。
    5. 如果子类现在扩展了在序列化数据中找不到的类,则父类字段将使用0/false/null值还原(如案例2所示)。

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

    Serializable 并且只有子类这样做,那么基类中的字段将按照 transient .

    推荐文章