Javaでは、クラスがSerializableを実装しているが抽象的である場合、serialVersionUIDを長く宣言する必要がありますか、それともサブクラスでそれだけが必要ですか?
この場合、タイプの目的がRMI呼び出しで使用されるため、すべてのサブクラスがシリアル化を処理することが意図されています。
シリアル化されたオブジェクトとクラスの現在のバージョンの間の互換性を判断するために、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
はい、一般に、他のクラスがシリアルIDを必要とするのと同じ理由で、シリアルIDが生成されないようにします。基本的に、serializableを実装するすべてのクラス(インターフェースではない)は、シリアルバージョンIDを定義する必要があります。そうしないと、同じ.classコンパイルがサーバーとクライアントのJVMにない場合、逆シリアル化エラーが発生する可能性があります。
何かおかしなことをしようとしている場合は、他のオプションがあります。 「それはサブクラスの意図です...」という意味がわかりません。カスタムのシリアル化メソッド(writeObject、readObjectなど)を記述しますか?もしそうなら、スーパークラスを扱うための他のオプションがあります。
参照: http://Java.Sun.com/javase/6/docs/api/Java/io/Serializable.html
HTHトム
実際、欠落しているserialVersionID
が実際にはシリアライゼーションランタイムによって計算された場合、つまりコンパイル時ではない場合、Tomのリンクの外を指す
シリアル化可能なクラスがserialVersionUIDを明示的に宣言していない場合、シリアル化ランタイムは、クラスのさまざまな側面に基づいて、そのクラスのデフォルトのserialVersionUID値を計算します...
これにより、JREのバージョンが異なると、事態がさらに複雑になります。
概念的には、シリアル化されたデータは次のようになります。
subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)
したがって、逆シリアル化すると、階層内のいずれかのクラスのバージョンが一致しないため、逆シリアル化が失敗します。インターフェイスには何も保存されないため、バージョンを指定する必要はありません。
Answer:はい、基本抽象クラスでもserialVersionUID
を提供する必要があります。フィールドがなくても(className + version
は、フィールドがない場合でも格納されます)。
次の点にも注意してください。
Object
値を持つString
フィールドがある場合、フィールドタイプをString
に変更すると成功しますが、Integer
に変更すると成功しません。ただし、int
値をlong
変数に割り当てることができても、フィールドをint
からlong
に変更しても機能しません。簡単に言うと、フィールドを並べ替えたり、フィールドを追加および削除したり、クラス階層を変更したりできます。フィールドまたはクラスの名前を変更しないでください(失敗しませんが、値は逆シリアル化されません)。プリミティブ型のフィールドの型を変更することはできません。すべての値から新しい型が割り当て可能であれば、参照型フィールドを変更できます。
注:基本クラスがSerializableを実装せず、サブクラスのみが実装する場合、基本クラスのフィールドはtransient
として動作します。