java 生成 シリアルVersionUIDとは何ですか?




serialversionuid 変更 (17)

Josh Blochの本「 Effective Java (第2版)」をこの機会に譲ることはできません。 第11章は、Javaのシリアライズに不可欠なリソースです。

Joshでは、自動的に生成されたUIDは、クラス名、実装されたインタフェース、およびすべてのpublicおよびprotectedメンバーに基づいて生成されます。 これらのいずれかを変更すると、 serialVersionUIDが変更さserialVersionUIDます。 したがって、クラスのバージョンが1つしかシリアル化されないことが確実である場合(プロセス間でも、後でストレージから取得してもどちらでもかまいません)、確実である場合にのみ、それらを混乱させる必要はありません。

今のところそれらを無視して、何らかの方法でクラスを変更する必要があるが、古いバージョンのクラスとの互換性を維持する必要がある場合は、JDKツールのserialverを使用して古いクラスのserialVersionUIDを生成し、新しいクラスのそれ。 (変更に応じて、 writeObjectreadObjectメソッドを追加してカスタムシリアル化を実装する必要があるかもしれません - Serializable javadocまたは前述の第11章を参照してください)

serialVersionUIDがない場合、Eclipseは警告を出します。

直列化可能クラスFooは、long型のstatic final serialVersionUIDフィールドを宣言しません

serialVersionUIDとは何ですか?それはなぜ重要ですか? serialVersionUIDが見つからない場合に問題が発生する例を示してください。


シリアライズを考えていないクラスでこの警告が表示され、自分で宣言していないことがimplements Serializableている場合は、 implements Serializableなスーパークラスから継承されていることがよくあります。 多くの場合、継承を使用する代わりに、そのようなオブジェクトに委譲するほうがよいでしょう。

だから、代わりに

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

行う

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

関連するメソッドでthis.foo() (またはsuper.foo() )ではなくmyList.foo()呼び出します。 (これはすべての場合に当てはまるわけではありませんが、かなりの頻度で行われます)。

私はしばしばJFrameなどを拡張している人たちが実際にこれに委任する必要があるだけです。 (これはIDEでの自動補完にも役立ちます。なぜなら、JFrameには何百ものメソッドがあります。これはクラスでカスタムメソッドを呼び出すときには不要です。)

警告(またはserialVersionUID)が避けられない場合は、通常は匿名クラスのAbstractActionから継承し、actionPerformedメソッドを追加するだけです。 私はこのケースでは警告を出すべきではないと思います。(なぜなら、あなたは通常、クラスの異なるバージョン間でこのような匿名クラスを直列化して非直列化することはできませんが)コンパイラがこれをどのように認識できるかはわかりません。


java.io.Serializableのドキュメントは、おそらくあなたが得られるほど良い説明です:

シリアライズ・ランタイムは、シリアライズ可能な各クラスに、serialVersionUIDと呼ばれるバージョン番号を関連付けます。このバージョン番号は、シリアル化されたオブジェクトの送信者と受信者がシリアル化に対して互換性のあるオブジェクトのクラスをロードしたことを検証するために、 受信側が、対応する送信側のクラスと異なるserialVersionUIDを持つオブジェクトのクラスをロードした場合、非直列化によりInvalidClassExceptionが発生します。 直列化可能クラスは、static、final、およびlong型でなければならない " serialVersionUID "という名前のフィールドを宣言することによって、それ自身のserialVersionUIDを明示的に宣言できます。

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

直列化可能クラスがserialVersionUIDを明示的に宣言しない場合、直列化ランタイムは、Java(TM)オブジェクト直列化仕様で説明されているように、クラスのさまざまな側面に基づいてそのクラスのデフォルトserialVersionUID値を計算します。 ただし、デフォルトのserialVersionUIDの計算は、コンパイラの実装によって異なる可能性があるクラスの詳細に対して非常に敏感で、非直列化中に予期しないInvalidClassExceptionsが発生する可能性があるため、すべての直列化可能なクラスがserialVersionUID値を明示的に宣言することを強くお勧めします。 したがって、異なるJavaコンパイラ実装間で一貫したserialVersionUID値を保証するためには、直列化可能クラスで明示的なserialVersionUID値を宣言する必要があります。 また、明示的なserialVersionUID宣言は、可能であればprivate修飾子を使用することを強く推奨します。そのような宣言は、直ちに宣言するクラスにのみ適用されます - serialVersionUIDフィールドは、継承されたメンバーとしては有用ではありません。


実装のためにシリアル化する必要があるだけでシリアル化する必要がある場合(たとえば、HTTPSessionのシリアル化を行う場合、ストアされているかどうかに関わらず、フォームオブジェクトの逆シリアル化は気にしません)それを無視することができます。

実際にシリアライゼーションを使用している場合は、シリアライゼーションを直接使用してオブジェクトを格納および取得する予定がある場合にのみ重要です。 serialVersionUIDはクラスのバージョンを表します。クラスの現在のバージョンが以前のバージョンと下位互換性がない場合は、それを増やす必要があります。

ほとんどの場合、おそらくシリアル化を直接使用することはありません。 このような場合は、クイックフィックスオプションをクリックしてデフォルトのシリアライズ可能なuidを生成し、それについて心配しないでください。


欠落しているserialVersionUIDが問題を引き起こす可能性がある例については、

私はEJBモジュールを使用するWebモジュールで構成されるこのJava EEアプリケーションを開発中です。 WebモジュールはEJBモジュールをリモートで呼び出し、 Serializableを実装するPOJOを引数として渡します。

このPOJO'sクラスは、EJB jarの内部にパッケージ化されており、内部にはWebモジュールのWEB-INF / lib内の独自のjarが入っています。 実際には同じクラスですが、EJBモジュールをパッケージ化するときに、このPOJOのjarファイルを展開して、EJBモジュールと一緒にパッケージ化します。

私はそのserialVersionUID宣言していなかったので、以下のExceptionでEJBへの呼び出しが失敗しました:

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

私は一般的に、一つのコンテキストでserialVersionUIDを使用します:それがJava VMのコンテキストを離れることがわかったら。

私は私のアプリケーションにObjectInputStreamObjectOutputStreamを使用するとき、または私が使用するライブラリ/フレームワークを知っているとき、これを知っているだろう。 serialVersionIDは、さまざまなバージョンまたはベンダの異なるJava VMが相互に正しく動作するようにします。たとえば、 HttpSessionのようにVMの外部に格納および取得された場合、セッションデータはアプリケーションサーバーの再起動およびアップグレード中も保持されます。

それ以外の場合は、

@SuppressWarnings("serial")

ほとんどの場合、デフォルトのserialVersionUIDで十分です。 これにはExceptionHttpServlet含まれException


フィールドデータは、クラスに格納されている情報を表します。 クラスはSerializableインターフェースを実装しているので、eclipseは自動的にserialVersionUIDフィールドを宣言します。 そこに設定された値1で始めることができます。

その警告が来ないようにするには、次のようにします:

@SuppressWarnings("serial")

CheckStyleがSerializableを実装しているクラスのserialVersionUIDが良い値を持っていること、つまりシリアルバージョンのIDジェネレータが生成するものと一致することをCheckStyleが確認できればいいです。たとえば、既存のserialVersionUIDを削除して再生成することを覚えていて、これを確認するための唯一の方法(私が知っている)は、クラスごとに再生成して古いもの。これは非常に苦痛です。


IntelliJ Ideaのような古いクラスとの互換性を維持しながら、最初にserialVersionUIDが設定されていない膨大な数のクラスを修正したい場合、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

このスクリプトを保存して、〜/ binにadd_serialVersionUID.shと言ってください。次に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にあることを確認してください。


JavaでSerialVersionUID内部Serializableクラスを使用する理由

その間serialization、Javaランタイムは、クラスのバージョン番号を作成します。これにより、後でそれを逆シリアル化できます。このバージョン番号はSerialVersionUIDJavaで知られています。

SerialVersionUIDシリアル化されたデータをバージョンするために使用されます。クラスSerialVersionUIDがシリアライズされたインスタンスと一致する場合は、クラスをデシリアライズできません。SerialVersionUID私たちがクラスで宣言していないとき、Javaランタイムはそれを生成しますが、推奨されません。デフォルトのメカニズムを避けるためSerialVersionUIDprivate static final long変数として宣言することをお勧めします。

Serializableマーカーインターフェイスを実装することによってクラスを宣言するとjava.io.Serializable、Javaランタイムは、Externalizableインターフェイスを使用してプロセスをカスタマイズしていない場合、デフォルトのシリアライズメカニズムを使用して、そのクラスのインスタンスをディスクに保持します。

また、JavaでSerializableクラス内でSerialVersionUIDを使用する理由も参照してください

コード:javassist.SerialVersionUID


この質問は、Joshua BlochによるEffective Javaで非常によく説明されています。非常に良い本と読む必要があります。私は以下の理由のいくつかを概説します:

シリアライゼーション・ランタイムには、シリアライズ可能なクラスごとにシリアル・バージョンと呼ばれる番号が付いています。この番号はserialVersionUIDと呼ばれます。今度はこの数の後ろにいくつかの数学があり、クラスで定義されているフィールド/メソッドに基づいています。同じクラスの場合、毎回同じバージョンが生成されます。この数値は、シリアル化されたオブジェクトの送信者と受信者がシリアル化に関して互換性のあるオブジェクトのクラスをロードしたことを検証するために、直列化復元中に使用されます。受信側が、対応する送信側のクラスと異なるserialVersionUIDを持つオブジェクトのクラスをロードした場合、非直列化によりInvalidClassExceptionが発生します。

クラスがシリアライズ可能な場合は、static、final、およびlong型でなければならない "serialVersionUID"という名前のフィールドを宣言することによって、独自のserialVersionUIDを明示的に宣言することもできます。EclipseのようなほとんどのIDEは長い文字列を生成するのに役立ちます。


オブジェクトがシリアライズされるたびに、オブジェクトにはオブジェクトのクラスのバージョンID番号がスタンプされます。このIDはserialVersionUIDと呼ばserialVersionUID、クラス構造に関する情報に基づいて計算されます。あなたがEmployeeクラスを作ったとし、それがバージョンID#333(JVMによって割り当てられている)を持っているとしたら、そのクラスのオブジェクト(Employeeオブジェクトと仮定)をシリアル化するとき、JVMはUIDを#333として割り当てます。

状況を考えてみましょう。将来はクラスを編集したり変更したりする必要があります。その場合、JVMは新しいUIDを割り当てます(#444と仮定)。従業員オブジェクトを逆シリアル化しようとすると、JVMはシリアル化されたオブジェクト(Employeeオブジェクト)のバージョンID(#333)と#444(変更されて以降)のクラスIDを比較します。比較すると、JVMは両方のバージョンUIDが異なっていることがわかります。したがって、逆シリアル化は失敗します。したがって、各クラスのserialVersionIDがプログラマ自身によって定義されている場合。クラスが将来進化しても同じであるため、JVMは、クラスが変更されても、そのクラスが直列化されたオブジェクトと互換性があることを常に検出します。詳細については、HEAD FIRST JAVAの第14章を参照してください。


serialVersionUIDは、シリアル化されたデータのバージョン管理を容易にします。 その値は、シリアライズ時にデータとともに格納されます。 デシリアライズすると、同じバージョンがチェックされ、シリアライズされたデータが現在のコードとどのように一致するかが確認されます。

データのバージョンを設定するには、通常はserialVersionUIDを0にして、シリアル化されたデータを変更するクラスへの構造変更(非一時フィールドの追加または削除)を行います。

ビルトインの非直列化メカニズム( in.defaultReadObject() )は、古いバージョンのデータからデシリアライズを拒否します。 しかし、あなたが望むのであれば、独自のreadObject()関数を定義して、古いデータを読み戻すことができます。 このカスタムコードでは、 serialVersionUIDをチェックして、データのバージョンを知り、それをデシリアライズする方法を決定することができます。 このバージョン管理のテクニックは、コードのいくつかのバージョンで残っているシリアル化されたデータを保存する場合に便利です。

しかし、このような長時間にわたるシリアル化されたデータの格納はあまり一般的ではありません。 シリアライゼーションメカニズムを使用して、たとえばキャッシュにデータを一時的に書き込むか、またはコードベースの関連する部分と同じバージョンの別のプログラムにネットワーク経由でデータを送信することが、より一般的です。

この場合、下位互換性を維持することには関心がありません。 実際に通信しているコードベースに関連するクラスのバージョンが同じであることを確認することにのみ関心があります。 このようなチェックを容易にするためには、以前と同じようにserialVersionUID維持し、クラスを変更するときに更新することを忘れないでください。

フィールドを更新するのを忘れた場合、異なる構造を持ちながら同じserialVersionUID持つ2つの異なるバージョンのクラスで終わる可能性があります。 この場合、デフォルト機構( in.defaultReadObject() )は違いを検出せず、互換性のないデータのデシリアライズを試みます。 今や、潜在的なランタイムエラーまたはサイレントエラー(ヌルフィールド)が発生する可能性があります。 これらのタイプのエラーは見つけるのが難しいかもしれません。

このため、Javaプラットフォームでは、 serialVersionUID手動で設定しないでください。 その代わりに、クラス構造のハッシュがコンパイル時に生成され、idとして使用されます。 このメカニズムは、同じIDを持つ異なるクラス構造を持つことがないことを保証します。したがって、上記の難しいトレースの実行時シリアル化エラーは発生しません。

しかし、自動生成されたID戦略の裏側があります。 つまり、同じクラスの生成されたIDは、コンパイラによって異なる場合があります(上記のJon Skeetが述べたように)。 したがって、異なるコンパイラでコンパイルされたコード間でシリアル化されたデータをやりとりする場合は、手動でIDを維持することをお勧めします。

また、前述の最初のユースケースのようにデータと下位互換性がある場合は、おそらく自分でIDを管理したいと思うかもしれません。 これは、読み込み可能なIDを取得し、いつ、どのように変更するかをより詳細に制御できるようにするためです。


まず、シリアライゼーションについて説明する必要があります。
シリアライゼーションでは、オブジェクトをストリームに変換し、そのオブジェクトをネットワーク経由で送信したり、ファイルに保存したり、レター使用のためにDBに保存したりできます。

シリアライズにはいくつかの規則があります

  • オブジェクトは、そのクラスまたはスーパークラスがSerializableインタフェースを実装している場合にのみ、直列化可能です

  • オブジェクトは、そのスーパークラスが存在しない場合でも、それ自体がSerializableインタフェースを実装します。 しかし、Serializableインタフェースを実装していない直列化可能クラスの階層の最初のスーパークラスは、引数なしのコンストラクタを持っていなければなりません。 これに違反すると、readObject()は実行時にjava.io.InvalidClassExceptionを生成します

  • すべてのプリミティブ型は直列化可能です。

  • 一時フィールド(一時的修飾子付き)は直列化されません(つまり、保存または復元されません)。 Serializableを実装するクラスは、直列化をサポートしないクラス(例えば、ファイルストリーム)の一時フィールドをマークする必要があります。

  • 静的フィールド(静的修飾子付き)は直列化されません。

Objectがシリアル化されている場合JAVA Runtime serialVersionIDというシリアルバージョン番号を関連付けます。

serialVersionIDが必要な場所:送信者と受信者がシリアル化に関して互換性があることを検証するためのデシリアライゼーション中です 。受信者が異なるserialVersionIDを持つクラスをロードすると、デシリアライズはInvalidClassCastExceptionで終了します。
直列化可能クラスは、static、final、およびlong型でなければならない "serialVersionUID"という名前のフィールドを宣言することによって、独自のserialVersionUIDを明示的に宣言できます。

例を挙げてこれを試してみましょう。

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

シリアル化オブジェクトの作成

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

オブジェクトのDeserializ

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

注:EmployeeクラスのserialVersionUIDを変更して保存します。

private static final long serialVersionUID = **4L**;

そしてReaderクラスを実行します。 Writerクラスを実行しないと、例外が発生します。

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

元の質問では、このSerial Version IDが有用である場合、「なぜ重要か」と「例」が求められました。 まあ、私は1つを見つけました。

Carクラスを作成し、インスタンス化し、オブジェクトストリームに書き出すとします。 平坦化された車オブジェクトはしばらくの間ファイルシステムに座っています。 一方、 Carクラスが新しいフィールドを追加することによって変更された場合。 後で、フラット化されたCarオブジェクトを読み込み(つまり逆シリアル化)しようとすると、 java.io.InvalidClassExceptionが発生します。これは、すべてのシリアライズ可能クラスには自動的に一意の識別子が与えられるからです。 この例外は、クラスの識別子が平坦化されたオブジェクトの識別子と等しくない場合にスローされます。 あなたが本当にそれについて考えるならば、新しいフィールドの追加のために例外がスローされます。 明示的なserialVersionUIDを宣言することによって、あなた自身のバージョン管理を制御することによって、この例外がスローされることを避けることができます。 serialVersionUIDを明示的に宣言する際のパフォーマンス上のメリットもわずかです(計算する必要がないため)。 したがって、以下のように作成するとすぐに、独自のserialVersionUIDをSerializableクラスに追加することをお勧めします。

public class Car {
static final long serialVersionUID = 1L; //assign a long value
}

気にしないでください、デフォルトの計算は本当に良いですし、99,9999%のケースで十分です。 問題が発生した場合は、既に述べたように、UIDを導入する必要はありません(これはほとんど起こりそうもない)


オブジェクトをバイト配列にシリアル化して送信/保存する必要がない場合は、それを心配する必要はありません。 そうした場合、オブジェクトのデシリアライザはそのクラスローダの持つオブジェクトのバージョンと一致するため、serialVersionUIDを考慮する必要があります。 そのことについては、Java言語仕様書を参照してください。





serialversionuid