Sollte eine abstrakte Klasse eine serialVersionUID haben

Lesezeit: 6 Minuten

Wenn in Java eine Klasse Serializable implementiert, aber abstrakt ist, sollte sie eine lange deklarierte serialVersionUID haben, oder erfordern die Unterklassen dies nur?

In diesem Fall ist es tatsächlich beabsichtigt, dass sich alle Unterklassen mit der Serialisierung befassen, da der Zweck des Typs darin besteht, in RMI-Aufrufen verwendet zu werden.

  • Ich fange immer wieder an, eine Antwort zu schreiben, und stelle dann fest, dass ich es nicht genau weiß, obwohl ich eine Ahnung habe. +1 für eine Frage, die ich nicht beantworten kann.

    – Michael myers

    21. Mai 2009 um 14:53 Uhr

Benutzer-Avatar
Bill die Eidechse

Die serialVersionUID wird bereitgestellt, um die Kompatibilität zwischen einem deseralisierten Objekt und der aktuellen Version der Klasse zu bestimmen. Als solches ist es in der ersten Version einer Klasse oder in diesem Fall in einer abstrakten Basisklasse nicht wirklich notwendig. Sie werden nie eine Instanz dieser abstrakten Klasse zum Serialisieren/Deserialisieren haben, daher ist keine serialVersionUID erforderlich.

(Natürlich generiert es eine Compiler-Warnung, die Sie loswerden möchten, oder?)

Es stellt sich heraus, dass James’ Kommentar richtig ist. Die serialVersionUID einer abstrakten Basisklasse tut an Unterklassen weitergegeben werden. In Anbetracht dessen, Sie tun benötigen Sie die serialVersionUID in Ihrer Basisklasse.

Der zu testende Code:

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

Führen Sie main einmal in Sub aus, damit es ein Objekt erstellt und speichert. Ändern Sie dann die serialVersionUID in der Basisklasse, kommentieren Sie die Zeilen in main aus, die das Objekt speichern (damit es nicht erneut gespeichert wird, Sie möchten nur das alte laden), und führen Sie es erneut aus. Dies führt zu einer Ausnahme

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

  • Gute Antwort… @SuppressWarnings(“serial”) unterdrückt die Warnmeldung

    – Ryan Anderson

    21. Mai 2009 um 15:01 Uhr

  • @Ryan: Danke, aber normalerweise behandle ich Warnungen wie Fehler und behandle sie direkt.

    – Bill die Eidechse

    21. Mai 2009 um 15:07 Uhr

  • …aber ich verstehe, dass nicht jeder so dogmatisch ist wie ich, daher ist Ihr Kommentar sehr willkommen.

    – Bill die Eidechse

    21. Mai 2009 um 15:08 Uhr

  • eigentlich ist das falsch. Während der Deserialisierung wird die serialversionuid aller Klassen in der Vererbungskette berücksichtigt, daher könnte das Fehlen einer in einer abstrakten Klasse problematisch sein. Ich bin tatsächlich auf dieses Problem gestoßen.

    – James

    21. Mai 2009 um 15:39 Uhr

  • Es sollte auch in der ersten Version der Klasse vorhanden sein, da die Neukompilierung mit einem anderen Compiler möglicherweise eine andere standardmäßige serialVersionUID erzeugt. Dadurch wird eine neu kompilierte Version der Klasse (ohne Codeänderungen) mit der alten inkompatibel. Überprüfen Sie die Notiz java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/…

    – Robin

    21. Mai 2009 um 16:27 Uhr

Ja, im Allgemeinen, aus dem gleichen Grund, aus dem jede andere Klasse eine Seriennummer benötigt – um zu vermeiden, dass eine dafür generiert wird. Grundsätzlich sollte jede Klasse (nicht Schnittstelle), die serialisierbar implementiert, die ID der seriellen Version definieren, oder Sie riskieren Deserialisierungsfehler, wenn sich nicht dieselbe .class-Kompilierung in den Server- und Client-JVMs befindet.

Es gibt andere Möglichkeiten, wenn Sie versuchen, etwas Ausgefallenes zu tun. Ich bin mir nicht sicher, was Sie mit “es ist die Absicht der Unterklassen …” meinen. Werden Sie benutzerdefinierte Serialisierungsmethoden schreiben (z. B. writeObject, readObject)? Wenn ja, gibt es andere Möglichkeiten, mit einer Superklasse umzugehen.

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

HTH Tom

Eigentlich auf Toms Link hinweisen, falls er fehlt serialVersionID wird tatsächlich von der Serialisierungslaufzeit berechnet, dh nicht während der Kompilierung

Wenn eine serialisierbare Klasse nicht explizit eine serialVersionUID deklariert, berechnet die Serialisierungslaufzeit einen standardmäßigen serialVersionUID-Wert für diese Klasse basierend auf verschiedenen Aspekten der Klasse …

Dies macht die Sache noch komplizierter, wenn Sie verschiedene Versionen von JRE haben.

Benutzer-Avatar
Oliv

Konzeptionell sehen die serialisierten Daten so aus:

subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)

Wenn Sie also deserialisieren, führt eine Nichtübereinstimmung in der Version in einer der Klassen in der Hierarchie dazu, dass die Deserialisierung fehlschlägt. Für Schnittstellen wird nichts gespeichert, daher muss für sie keine Version angegeben werden.

Die Antwort lautet also: Ja, Sie müssen es bereitstellen serialVersionUID in der abstrakten Basisklasse, auch wenn sie keine Felder hat: className + version ist noch gespeichert.

Beachten Sie auch Folgendes:

  1. Wenn eine Klasse kein Feld hat, das in den serialisierten Daten vorkommt (ein entferntes Feld), wird es ignoriert.
  2. Wenn eine Klasse ein Feld hat, das in den serialisierten Daten nicht vorhanden ist (ein neues Feld), wird es auf 0/false/null gesetzt. Es ist nicht wie erwartet auf den Standardwert gesetzt.
  3. Wenn ein Feld seinen Typ ändert, muss der deserialisierte Wert dem neuen Typ zuweisbar sein. Wenn Sie zB eine hatten Object Feld mit String Wert, Ändern des Feldtyps in String wird gelingen, aber ändern Sie es zu Integer Gewohnheit. Wechselfeld jedoch aus int zu long funktioniert nicht, obwohl Sie zuweisen können int Wert zu long Variable.
  4. Wenn eine Unterklasse die Elternklasse, die sie in den serialisierten Daten erweitert, nicht mehr erweitert, wird sie ignoriert (wie im Fall 1).
  5. Wenn nun eine Unterklasse eine Klasse erweitert, die nicht in den serialisierten Daten gefunden wird, werden die Felder der Elternklasse mit 0/False/Null-Wert wiederhergestellt (wie in Fall 2).

Mit einfachen Worten: Sie können Felder neu anordnen, hinzufügen und entfernen und sogar die Klassenhierarchie ändern. Sie sollten Felder oder Klassen nicht umbenennen (es wird nicht fehlschlagen, aber es wird behandelt, als ob dieses Feld entfernt und hinzugefügt wurde). Sie können den Typ von Feldern mit primitivem Typ nicht ändern, und Sie können Referenztypfelder ändern, sofern der neue Typ von allen serialisierten Werten zuweisbar ist.

Hinweis: Wenn die Basisklasse nicht implementiert Serializable und nur die Unterklasse tut, dann verhalten sich Felder aus der Basisklasse wie transient.

1176860cookie-checkSollte eine abstrakte Klasse eine serialVersionUID haben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy