JavaのシリアルバージョンUIDの用途と設定方法

java

実務で使用するjavaには、シリアルバージョンUIDが使われていることが良くあります。
シリアルバージョンUIDを知らなくともテストが通ってしまうことが多いと思うのですが、それだと思わぬ失敗をしかねないので、今回の記事で紹介したいと思います。

【シリアルバージョンUIDの用途】

javaではインスタンスをバイナリファイルとして保存することができます。
しかし、インスタンスのクラスの設計を変更した場合、前の設計のバイナリファイルを読み込むと、現在の設計のクラスとの整合性が取れずに処理結果が不正になる恐れがあります。

その際に、クラスにシリアルバージョンUIDを定義しておくことで、前の設計のバイナリファイルを読み込んだ際に例外を吐き出すことができるようになります。
シリアルバージョンUIDはバイナリファイルに保存されるのですが、現在のクラスのシリアルバージョンUIDとバイナリファイルのシリアルバージョンUIDが異なる場合に例外として判定されます。
そのため、クラスの設計を変更する度にシリアルバージョンUIDを変更していれば、前の設計のバイナリファイルを読み込んだ際に例外となります。

【テストコード】

以下、実際に挙動を確認した結果です。

■Item.java

package seriaTest;

import java.io.Serializable;

public class Item implements Serializable{

    private static final long serialVersionUID = 81923983183821L; //①
//    private static final long serialVersionUID = 81923983183822L; //②

    private String itemName;
    private int price;
//    private int stock; //②

    public Item(String itemName, int price) { //①
//    public Item(String itemName,  int price, int stock) { //②
        this.setItemName(itemName);
        this.setPrice(price);
//        this.setStock(stock);  //②
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
/*//②
    public long getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }
*///②
}

■MainInput.java

package seriaTest;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class MainInput {
    public static void main(String[] args) throws Exception {
        Item item1 = new Item("商品1",100); //①
//        Item item1 = new Item("商品1",100,10000); //②

        // インスタンスを保存
        FileOutputStream fos = new FileOutputStream("C:\\tmp\\instnce.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(item1);
        oos.flush();
        oos.close();

    }
}

■MainOutput.java

package seriaTest;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class MainOutput {
    public static void main(String[] args) throws Exception {

        // インスタンスを読み込み
        FileInputStream fis = new FileInputStream("C:\\tmp\\instnce.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Item item2 = (Item)ois.readObject();
        ois.close();

        System.out.println(item2.getItemName());
        System.out.println(item2.getPrice());
//        System.out.println(item2.getStock()); //②
    }
}

【実行結果】

■当初のクラス設計(①を有効、②をコメントアウト)

・MainInputを実行

インスタンスがファイルとして保存される。

C:\tmp>dir
 ドライブ C のボリューム ラベルは XXX です
 ボリューム シリアル番号は XXX です

 C:\tmp のディレクトリ

2018/12/01  10:18    <DIR>          .
2018/12/01  10:18    <DIR>          ..
2018/12/01  10:36                91 instnce.dat
               1 個のファイル                  91 バイト
               2 個のディレクトリ  24,059,305,984 バイトの空き領域

C:\tmp>
・MainOutputを実行

インスタンスを読み込めていることを確認できる。

商品1
100

■クラス設計変更(①をコメントアウト、②を有効)

・MainOutputを実行(変更前のクラス設計時のファイルを読み込んでみる)

インスタンス内に保存されたシリアルバージョンUIDが現在のクラスのシリアルバージョンUIDと異なるため、現在のクラスに対応したインスタンスではないと判断して異常終了する。
(シリアルバージョンUIDを変更しないと、現在のクラスに対応したインスタンスでなくても読み込み時にエラーにならずに後続に進んでしまう。不正なインスタンスを用いることで後続処理の処理結果が不正になる可能性が出てしまう。)

Exception in thread "main" java.io.InvalidClassException: seriaTest.Item; local class incompatible: stream classdesc serialVersionUID = 81923983183821, local class serialVersionUID = 81923983183822
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
    at seriaTest.MainOutput.main(MainOutput.java:12)
・MainInputを実行→MainOutputを実行

新しいクラスでインスタンスを作り直せば正常に処理できる。

商品1
100
10000

今回取り扱ったシリアルバージョンUIDもそうですが、研修で習うことは少なくても実務での使用頻度は高い、という文法は少なくありません。
そのような文法を見つけたら、また紹介したいと思います。

これからも、実務で役に立つ情報をお伝えできればと思います。
では、また来週!

コメント

タイトルとURLをコピーしました