Если у абстрактного класса есть serialVersionUID

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

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

Ответ 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() );
    }
}

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

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

Ответ 2

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

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

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

HTH Tom

Ответ 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 сохраняются, даже если нет полей).

Также обратите внимание на следующее:

  • Если класс не имеет поля, которое содержится в сериализованных данных (удаленное поле), оно игнорируется.
  • Если у класса есть поле, которое отсутствует в сериализованных данных (новое поле), оно устанавливается равным 0/false/null (не для значения по умолчанию, как можно было бы ожидать).
  • Если поле изменяет тип данных, десериализованное значение должно быть назначено новому типу. Например. если у вас есть поле Object со значением String, изменение типа поля на String будет успешным, но переход на Integer не будет. Однако изменение поля от int до long не будет работать, даже если вы можете назначить значение int переменной long.
  • Если подкласс больше не расширяет родительский класс, который он расширяет в сериализованных данных, он игнорируется (как в случае 1).
  • Если подкласс теперь расширяет класс, который не встречается в сериализованных данных, поля родительского класса восстанавливаются с 0/ложным/нулевым значением (как в случае 2).

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

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