Должен ли абстрактный класс иметь serialVersionUID - PullRequest
61 голосов
/ 21 мая 2009

В Java, если класс реализует Serializable, но является абстрактным, должен ли он иметь объявленный long для serialVersionUID или для подклассов требуется только это?

В этом случае действительно предполагается, что все подклассы имеют дело с сериализацией, поскольку назначение типа должно использоваться в вызовах RMI.

Ответы [ 4 ]

45 голосов
/ 21 мая 2009

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

Запустите main в Sub один раз, чтобы он создал и сохранил объект. Затем измените serialVersionUID в базовом классе, закомментируйте строки в main, которые сохраняют объект (чтобы он больше не сохранялся, вы просто хотите загрузить старый), и снова запустите. Это приведет к исключению

java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
6 голосов
/ 21 мая 2009

Да, в общем, по той же причине, по которой любой другой класс нуждается в серийном идентификаторе - чтобы избежать его создания. По сути, любой класс (не интерфейс), реализующий сериализуемый, должен определять идентификатор последовательной версии, иначе вы рискуете ошибками десериализации, если одна и та же компиляция .class отсутствует в JVM сервера и клиента.

Есть и другие варианты, если вы пытаетесь сделать что-то необычное. Я не уверен, что вы подразумеваете под "намерением подклассов ...". Собираетесь ли вы написать собственные методы сериализации (например, writeObject, readObject)? Если так, то есть другие варианты для работы с суперклассом.

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

HTH Tom

2 голосов
/ 30 апреля 2014

На самом деле, указание на ссылку Тома в случае отсутствия serialVersionID фактически рассчитывается во время выполнения сериализации, т.е.

Если сериализуемый класс явно не объявляет serialVersionUID, тогда среда выполнения сериализации вычислит значение serialVersionUID по умолчанию для этого класса на основе различных аспектов класса ...

Это еще более усложняет использование разных версий JRE.

0 голосов
/ 05 апреля 2017

Концептуально сериализованные данные выглядят так:

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 не будет работать, даже если вы можете присвоить int значение переменной long.
  4. Если подкласс больше не расширяет родительский класс, который он расширяет в сериализованных данных, он игнорируется (как в случае 1).
  5. Если подкласс теперь расширяет класс, которого нет в сериализованных данных, поля родительского класса восстанавливаются со значением 0 / false / null (как в случае 2).

Проще говоря: вы можете изменять порядок полей, добавлять и удалять их, даже изменять иерархию классов. Вы не должны переименовывать поля или классы (это не даст сбой, но значения не будут десериализованы). Вы не можете изменить тип полей с примитивным типом, а также вы можете изменить поля ссылочного типа при условии, что новый тип может быть назначен из всех значений.

Примечание: если базовый класс не реализует Serializable и только подкласс делает, то поля из базового класса будут вести себя как transient.

...