serialVersionUID
aが欠落 している場合、Eclipse は警告を発行します。
シリアライズ可能なクラス Foo は、long 型の static final serialVersionUID フィールドを宣言していません
それは何でserialVersionUID
、なぜ重要なのですか?serialVersionUID
欠落が問題を引き起こす例を示してください。
serialVersionUID
aが欠落 している場合、Eclipse は警告を発行します。
シリアライズ可能なクラス Foo は、long 型の static final serialVersionUID フィールドを宣言していません
それは何でserialVersionUID
、なぜ重要なのですか?serialVersionUID
欠落が問題を引き起こす例を示してください。
のドキュメントはjava.io.Serializable
、おそらくあなたが得るのと同じくらい良い説明です:
シリアライゼーション ランタイムは、各シリアライズ可能なクラスに と呼ばれるバージョン番号を関連付けます。このバージョン番号は、
serialVersionUID
デシリアライズ中に使用され、シリアライズされたオブジェクトの送信者と受信者が、シリアライゼーションに関して互換性のあるそのオブジェクトのクラスを読み込んでいるかどうかを確認します。serialVersionUID
レシーバーが、対応するセンダーのクラスとは 異なるオブジェクトのクラスをロードした場合、逆シリアル化はInvalidClassException
. シリアライズ可能なクラスは、 static 、 final 、および type でなければならないserialVersionUID
という名前のフィールドを宣言することにより、それ自体を明示的に宣言できます。serialVersionUID
long
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
シリアライズ可能クラスが を明示的に宣言しない場合、
serialVersionUID
シリアライゼーション ランタイムはserialVersionUID
、Java(TM) オブジェクト シリアライゼーション仕様で説明されているように、クラスのさまざまな側面に基づいてそのクラスのデフォルト値を計算します。ただし、デフォルトの計算は、コンパイラの実装によって異なる可能性があるクラスの詳細に非常に敏感であり、逆シリアル化中に予期しない結果になる可能性があるため、すべてのシリアル化可能なクラスで値を明示的に宣言することを強くお勧めします。したがって、さまざまな Java コンパイラの実装間で一貫した値を保証するには、シリアライズ可能なクラスで明示的な値を宣言する必要があります。また、明示的にserialVersionUID
serialVersionUID
InvalidClassExceptions
serialVersionUID
serialVersionUID
serialVersionUID
宣言は、可能な場合は private 修飾子を使用します。そのような宣言は、すぐに宣言するクラスにのみ適用されるためです —serialVersionUID
フィールドは、継承されたメンバーとしては役に立ちません。
実装のためにシリアル化する必要があるという理由だけでシリアル化する場合 (たとえば、HTTPSession
.de-serializing
これを無視してください。
実際にシリアライゼーションを使用している場合、シリアライゼーションを直接使用してオブジェクトを保存および取得することを計画している場合にのみ問題になります。はserialVersionUID
クラスのバージョンを表し、クラスの現在のバージョンが以前のバージョンと下位互換性がない場合は、それをインクリメントする必要があります。
ほとんどの場合、シリアル化を直接使用することはないでしょう。このような場合はSerialVersionUID
、クイック修正オプションをクリックしてデフォルトを生成してください。心配する必要はありません。
Josh Bloch の本「Effective Java (2nd Edition)」を紹介するこの機会を逃すわけにはいきません。第 10 章は、Java シリアライゼーションに関する不可欠なリソースです。
Josh によると、自動生成された UID は、クラス名、実装されたインターフェイス、およびすべてのパブリック メンバーと保護されたメンバーに基づいて生成されます。これらのいずれかを何らかの方法で変更すると、serialVersionUID
. したがって、クラスの複数のバージョンがシリアル化されないことが確実な場合にのみ、それらをいじる必要はありません (プロセス間または後でストレージから取得されます)。
今のところそれらを無視し、後で何らかの方法でクラスを変更する必要があるが、古いバージョンのクラスとの互換性を維持する必要があることがわかった場合は、JDK ツールのserialverserialVersionUID
を使用して古いクラスでを生成し、それを明示的に設定できます。新しいクラスについて。writeObject
(変更によっては、 メソッドとreadObject
メソッドを追加してカスタムのシリアル化も実装する必要がある場合がありますSerializable
。javadoc または前述の第 10 章を参照してください。)
これらのserialVersionUID警告を無視するようにEclipseに指示できます。
ウィンドウ>設定>Java>コンパイラ>エラー/警告>潜在的なプログラミングの問題
知らなかった場合は、このセクションで有効にできる他の多くの警告があります(または、エラーとして報告されるものもあります)。多くは非常に便利です。
などなど。
serialVersionUID
シリアル化されたデータのバージョン管理を容易にします。その値は、シリアライズ時にデータとともに保存されます。逆シリアル化するとき、シリアル化されたデータが現在のコードとどのように一致するかを確認するために、同じバージョンがチェックされます。
データをバージョン管理する場合、通常serialVersionUID
は 0 から開始し、シリアル化されたデータを変更する (非一時的なフィールドの追加または削除) クラスへのすべての構造変更でそれを増やします。
組み込みの逆シリアル化メカニズム ( in.defaultReadObject()
) は、古いバージョンのデータからの逆シリアル化を拒否します。ただし、必要に応じて、古いデータを読み戻すことができる独自のreadObject()関数を定義できます。このカスタム コードはserialVersionUID
、データがどのバージョンにあるかを確認し、デシリアライズする方法を決定するために をチェックできます。このバージョン管理手法は、コードの複数のバージョンに耐えられるシリアル化されたデータを保存する場合に役立ちます。
しかし、シリアライズされたデータをこのように長期間保存することはあまり一般的ではありません。シリアライゼーション メカニズムを使用して、たとえばキャッシュにデータを一時的に書き込んだり、コードベースの関連部分の同じバージョンを持つ別のプログラムにネットワーク経由で送信したりすることは、はるかに一般的です。
この場合、下位互換性の維持には関心がありません。通信しているコードベースが実際に関連するクラスの同じバージョンを持っていることを確認することだけに関心があります。このようなチェックを容易にするために、serialVersionUID
以前と同じように維持し、クラスに変更を加えるときに更新することを忘れないようにする必要があります。
フィールドを更新するのを忘れると、構造は異なるが同じserialVersionUID
. これが発生した場合、デフォルトのメカニズム ( in.defaultReadObject()
) は違いを検出せず、互換性のないデータを逆シリアル化しようとします。ここで、不可解なランタイム エラーまたはサイレント エラー (null フィールド) が発生する可能性があります。これらのタイプのエラーは、見つけるのが難しい場合があります。
serialVersionUID
したがって、このユースケースを支援するために、Java プラットフォームでは手動で設定しないという選択肢が用意されています。代わりに、クラス構造のハッシュがコンパイル時に生成され、id として使用されます。このメカニズムにより、同じ ID を持つ異なるクラス構造が存在しないことが保証されるため、前述の追跡が困難なランタイム シリアライゼーション エラーが発生することはありません。
しかし、自動生成 ID 戦略には裏があります。つまり、同じクラスに対して生成された ID がコンパイラ間で異なる可能性があるということです (上記の Jon Skeet が述べたように)。そのため、異なるコンパイラでコンパイルされたコード間でシリアル化されたデータをやり取りする場合は、とにかく ID を手動で維持することをお勧めします。
また、前述の最初の使用例のように、データとの下位互換性がある場合は、おそらく ID を自分で維持したいと思うでしょう。これは、読み取り可能な ID を取得し、変更のタイミングと方法をより詳細に制御するためです。
元の質問では、「なぜ重要なのか」と、これSerial Version ID
が役立つ「例」が求められました。さて、私はそれを見つけました。
クラスを作成しCar
、インスタンス化し、オブジェクト ストリームに書き出すとします。フラット化された車のオブジェクトは、ファイル システムにしばらく置かれます。一方、Car
新しいフィールドを追加してクラスが変更された場合。Car
後で、フラット化されたオブジェクトを読み取ろうとすると (つまり、逆シリアル化)、シリアル化可能なjava.io.InvalidClassException
すべてのクラスに一意の識別子が自動的に与えられるため、- が返されます。この例外は、クラスの識別子がフラット化されたオブジェクトの識別子と等しくない場合にスローされます。よくよく考えてみると、新しいフィールドが追加されたために例外がスローされます。明示的な serialVersionUID を宣言してバージョン管理を自分で制御することにより、この例外がスローされるのを回避できます。を明示的に宣言すると、パフォーマンスがわずかに向上します。serialVersionUID
(計算する必要がないため)。そのため、以下に示すように、作成したらすぐに独自の serialVersionUID を Serializable クラスに追加することをお勧めします。
public class Car {
static final long serialVersionUID = 1L; //assign a long value
}
オブジェクトをバイト配列にシリアル化して送信/保存する必要がない場合は、心配する必要はありません。その場合、オブジェクトのデシリアライザーがそれをクラスローダーが持っているオブジェクトのバージョンに一致させるため、serialVersionUID を考慮する必要があります。詳細については、Java 言語仕様を参照してください。
シリアライズについて考えたことがなく、自分自身を宣言していないクラスでこの警告が表示される場合implements Serializable
は、多くの場合、Serializable を実装するスーパークラスから継承したことが原因です。多くの場合、継承を使用する代わりに、そのようなオブジェクトに委譲する方が適切です。
だから、代わりに
public class MyExample extends ArrayList<String> {
public MyExample() {
super();
}
...
}
行う
public class MyExample {
private List<String> myList;
public MyExample() {
this.myList = new ArrayList<String>();
}
...
}
関連するメソッドでは(または)myList.foo()
の代わりに呼び出します。(これはすべての場合に当てはまるわけではありませんが、それでもかなりの場合に当てはまります。)this.foo()
super.foo()
本当にこれに委譲するだけでよいのに、JFrame などを拡張している人をよく見かけます。(これは、IDE でのオートコンプリートにも役立ちます。JFrame には何百ものメソッドがあり、クラスでカスタム メソッドを呼び出す場合には必要ありません。)
警告 (または serialVersionUID) が避けられないケースの 1 つは、通常は匿名クラスで、actionPerformed メソッドのみを追加して、AbstractAction から拡張する場合です。この場合、警告は表示されるべきではないと思います (通常、クラスの異なるバージョン間でこのような匿名クラスを確実にシリアライズおよびデシリアライズすることはできないため) が、コンパイラがこれをどのように認識できるかはわかりません。
フィールド serialVersionUID の重要性を理解するには、シリアライゼーション/デシリアライゼーションがどのように機能するかを理解する必要があります。
Serializable クラス オブジェクトがシリアライズされると、Java ランタイムはシリアル バージョン番号 (serialVersionUID と呼ばれる) をこのシリアライズされたオブジェクトに関連付けます。このシリアライズされたオブジェクトをデシリアライズするとき、Java ランタイムは、シリアライズされたオブジェクトの serialVersionUID とクラスの serialVersionUID を一致させます。両方が等しい場合は、デシリアライゼーションのさらなるプロセスに進むだけで、そうでない場合は InvalidClassException がスローされます。
したがって、シリアライゼーション/デシリアライゼーション プロセスを成功させるには、シリアライズされたオブジェクトの serialVersionUID がクラスの serialVersionUID と同等である必要があると結論付けています。プログラマーがプログラムで serialVersionUID 値を明示的に指定した場合、シリアライゼーションおよびデシリアライゼーション プラットフォームに関係なく、同じ値がシリアライズされたオブジェクトとクラスに関連付けられます (たとえば、シリアライゼーションは、sun またはMS JVM とデシリアライゼーションは、Zing JVM を使用する別のプラットフォームの Linux 上にある可能性があります)。
ただし、プログラマが serialVersionUID を指定していない場合、任意のオブジェクトの Serialization\DeSerialization を実行しているときに、Java ランタイムは独自のアルゴリズムを使用して計算します。この serialVersionUID 計算アルゴリズムは、JRE ごとに異なります。オブジェクトがシリアライズされる環境が 1 つの JRE (例: SUN JVM) を使用しており、デシリアライズが行われる環境が Linux Jvm(zing) を使用している可能性もあります。このような場合、シリアライズされたオブジェクトに関連付けられた serialVersionUID は、デシリアライズ環境で計算されたクラスの serialVersionUID とは異なります。逆シリアル化は成功しません。したがって、このような状況/問題を回避するために、プログラマーは常に Serializable クラスの serialVersionUID を指定する必要があります。
serialVersionUID の欠落が問題を引き起こす可能性がある例については、次のとおりです。
モジュールを使用する Web モジュールで構成されるこの Java EE アプリケーションに取り組んでいEJB
ます。Web モジュールはEJB
モジュールをリモートで呼び出し、POJO
実装する a をSerializable
引数として渡します。
このPOJO's
クラスは、EJB jar 内および Web モジュールの WEB-INF/lib 内の独自の jar 内にパッケージ化されました。これらは実際には同じクラスですが、EJB モジュールをパッケージ化するときに、この POJO の jar をアンパックして、EJB モジュールと一緒にパックします。
EJB
を宣言していなかったため、への呼び出しは以下の例外で失敗していましたserialVersionUID
。
Caused by: java.io.IOException: Mismatched serialization UIDs : Source
(Rep.
IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
= 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
= 6227F23FA74A9A52
気にしないでください。デフォルトの計算は非常に適切で、99,9999%のケースで十分です。また、問題が発生した場合は、すでに述べたように、必要に応じてUIDを導入できます(ほとんどありません)。
私は通常serialVersionUID
、1 つのコンテキストで使用します。Java VM のコンテキストを離れることがわかっている場合。
ObjectInputStream
これは、自分のアプリケーションで使用するときObjectOutputStream
、または使用するライブラリ/フレームワークがそれを使用することを知っている場合に知っています。serialVersionID は、さまざまなバージョンまたはベンダーの異なる Java VM が正しく相互運用されること、または VM の外部で格納および取得される場合 (たとえばHttpSession
、アプリケーション サーバーの再起動およびアップグレード中もセッション データが保持される場合) を保証します。
他のすべての場合、私は使用します
@SuppressWarnings("serial")
ほとんどの場合、デフォルトserialVersionUID
で十分です。これにはException
、が含まれHttpServlet
ます。
フィールド データは、クラスに格納されている一部の情報を表します。クラスはSerializable
インターフェースを実装するため、Eclipse はserialVersionUID
フィールドを宣言するために自動的に提供されます。そこに設定された値1から始めましょう。
その警告が表示されたくない場合は、これを使用します。
@SuppressWarnings("serial")
SerialVersionUID は、オブジェクトのバージョン管理に使用されます。クラスファイルで serialVersionUID を指定することもできます。serialVersionUID を指定しないと、クラスのフィールドを追加または変更すると、新しいクラスと古いシリアル化されたオブジェクトに対して生成された serialVersionUID が異なるため、既にシリアル化されているクラスを回復できなくなります。Java シリアライゼーション プロセスは、シリアライズされたオブジェクトの状態を回復するために正しい serialVersionUID に依存し、serialVersionUID が一致しない場合は java.io.InvalidClassException をスローします。
続きを読む: http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ
CheckStyleが、Serializableを実装するクラスのserialVersionUIDに適切な値があること、つまり、シリアルバージョンIDジェネレーターが生成する値と一致することを確認できれば便利です。たとえば、シリアル化可能なDTOが多数あるプロジェクトがある場合、既存のserialVersionUIDを削除して再生成することを覚えておくのは面倒です。現在、これを確認する唯一の方法(私が知っている)は、クラスごとに再生成して、古いもの。これは非常に苦痛です。
古いクラスとの互換性を維持しながら、そもそも serialVersionUID が設定されていない膨大な数のクラスを修正したい場合、IntelliJ Idea や Eclipse などのツールは、乱数を生成し、多数のファイルに対して機能しないため不十分です。一度に。serialVersionUID の問題を簡単に修正するために、次の bash スクリプトを作成します (Windows ユーザーには申し訳ありませんが、Mac を購入するか、Linux に変換することを検討してください)。
base_dir=$(pwd)
src_dir=$base_dir/src/main/java
ic_api_cp=$base_dir/target/classes
while read f
do
clazz=${f//\//.}
clazz=${clazz/%.java/}
seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
perl -ni.bak -e "print $_; printf qq{%s\n}, q{ private $seruidstr} if /public class/" $src_dir/$f
done
このスクリプトを保存します。たとえば、add_serialVersionUID.sh を ~/bin に保存します。次に、Maven または Gradle プロジェクトのルート ディレクトリで次のように実行します。
add_serialVersionUID.sh < myJavaToAmend.lst
この .lst には、serialVersionUID を追加するための Java ファイルのリストが次の形式で含まれています。
com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java
このスクリプトは、内部で JDK serialVer ツールを使用します。$JAVA_HOME/bin が PATH にあることを確認してください。
オブジェクトがシリアル化されるたびに、オブジェクトのクラスのバージョン ID 番号がオブジェクトにスタンプされます。この ID はserialVersionUIDと呼ばれ、クラス構造に関する情報に基づいて計算されます。Employee クラスを作成し、そのバージョン ID が #333 (JVM によって割り当てられた) であるとします。そのクラスのオブジェクト (従業員オブジェクトと仮定) をシリアル化すると、JVM はそれに #333 として UID を割り当てます。
状況を考えてみてください。将来、クラスを編集または変更する必要があり、その場合、クラスを変更すると、JVM はそれに新しい UID を割り当てます (#444 を想定)。ここで、employee オブジェクトをデシリアライズしようとすると、JVM はシリアライズされたオブジェクト (Employee オブジェクト) のバージョン ID (#333) をクラスのバージョン ID (#444) (Since it was changed) と比較します。比較すると、JVM は両方のバージョン UID が異なることを検出するため、デシリアライゼーションは失敗します。したがって、各クラスの serialVersionID がプログラマ自身によって定義されている場合。クラスが将来進化しても同じであるため、クラスが変更されても、JVMはクラスがシリアル化されたオブジェクトと互換性があることを常に検出します。詳細については、HEAD FIRST JAVA の第 14 章を参照してください。
簡単に言うと、このフィールドは、シリアル化されたデータを正しく逆シリアル化できるかどうかを確認するために使用されます。シリアライゼーションとデシリアライゼーションは、多くの場合、プログラムの異なるコピーによって行われます。たとえば、サーバーはオブジェクトを文字列に変換し、クライアントは受け取った文字列をオブジェクトに変換します。このフィールドは、このオブジェクトが何であるかについて、両方が同じ考えで動作することを示しています。このフィールドは、次の場合に役立ちます。
プログラムのさまざまなコピーがさまざまな場所にある場合 (1 つのサーバーと 100 のクライアントなど)。オブジェクトを変更し、バージョン番号を変更し、このクライアントを更新するのを忘れた場合、オブジェクトはデシリアライズできないことがわかります。
データをいくつかのファイルに保存し、後で変更されたオブジェクトを含むプログラムの更新されたバージョンでそれを開こうとすると、バージョンを正しく保っていれば、このファイルには互換性がないことがわかります。
重要なのはいつですか?
最も明白なことは、オブジェクトにいくつかのフィールドを追加すると、オブジェクト構造にこれらのフィールドがないため、古いバージョンではそれらを使用できなくなることです。
あまり目立たない - オブジェクトを逆シリアル化すると、文字列に存在しないフィールドは NULL として保持されます。オブジェクトからフィールドを削除した場合、古いバージョンはこのフィールドを常に NULL として保持し、古いバージョンがこのフィールドのデータに依存している場合、誤動作につながる可能性があります (とにかく、楽しみのためだけでなく、何かのために作成しました :-) )
目立たない - フィールドの意味に入れるアイデアを変更することがあります。たとえば、12 歳のときは「バイク」の下に「自転車」を意味しますが、18 歳のときは「オートバイ」を意味します。たとえば、友達が「街を自転車で走る」に誘ってくれて、あなただけが自転車に乗るとします。自転車で来て、分野を超えて同じ意味を保つことがいかに重要かを理解するでしょう :-)