web-dev-qa-db-ja.com

抽象クラスにserialVersionUIDがあるか

Javaでは、クラスがSerializableを実装しているが抽象的である場合、serialVersionUIDを長く宣言する必要がありますか、それともサブクラスでそれだけが必要ですか?

この場合、タイプの目的がRMI呼び出しで使用されるため、すべてのサブクラスがシリアル化を処理することが意図されています。

62
Yishai

シリアル化されたオブジェクトとクラスの現在のバージョンの間の互換性を判断するために、serialVersionUIDが提供されています。 そのため、クラスの最初のバージョン、またはこの場合は抽象基本クラスでは、これは実際には必要ありません。シリアル化/逆シリアル化するその抽象クラスのインスタンスは決してないので、serialVersionUIDは必要ありません。

(もちろん、コンパイラ警告を生成しますが、それを取り除きますか?)

ジェームズのコメントは正しいことがわかりました。抽象基本クラスのserialVersionUID doesはサブクラスに伝達されます。その点を踏まえると、ベースクラスにdoが必要です。

テストするコード:

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でメインを1回実行して、オブジェクトを作成および保存します。次に、BaseクラスのserialVersionUIDを変更し、オブジェクトを保存するmainの行をコメントアウトして(オブジェクトを再度保存せず、古いオブジェクトをロードするだけです)、再度実行します。これは例外になります

Java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
46
Bill the Lizard

はい、一般に、他のクラスがシリアルIDを必要とするのと同じ理由で、シリアルIDが生成されないようにします。基本的に、serializableを実装するすべてのクラス(インターフェースではない)は、シリアルバージョンIDを定義する必要があります。そうしないと、同じ.classコンパイルがサーバーとクライアントのJVMにない場合、逆シリアル化エラーが発生する可能性があります。

何かおかしなことをしようとしている場合は、他のオプションがあります。 「それはサブクラスの意図です...」という意味がわかりません。カスタムのシリアル化メソッド(writeObject、readObjectなど)を記述しますか?もしそうなら、スーパークラスを扱うための他のオプションがあります。

参照: http://Java.Sun.com/javase/6/docs/api/Java/io/Serializable.html

HTHトム

6
Tom

実際、欠落しているserialVersionIDが実際にはシリアライゼーションランタイムによって計算された場合、つまりコンパイル時ではない場合、Tomのリンクの外を指す

シリアル化可能なクラスがserialVersionUIDを明示的に宣言していない場合、シリアル化ランタイムは、クラスのさまざまな側面に基づいて、そのクラスのデフォルトのserialVersionUID値を計算します...

これにより、JREのバージョンが異なると、事態がさら​​に複雑になります。

2
Viktor Stolbin

概念的には、シリアル化されたデータは次のようになります。

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

したがって、逆シリアル化すると、階層内のいずれかのクラスのバージョンが一致しないため、逆シリアル化が失敗します。インターフェイスには何も保存されないため、バージョンを指定する必要はありません。

Answer:はい、基本抽象クラスでも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として動作します。

0
Oliv