Javaでは Serializable インターフェイスを実装したクラスは、シリアライズ(日本語では直列化と呼ぶ)という操作で、メモリ上の Java のオブジェクトをハードディスクなどの記憶媒体に永続的に保存することができます。
この記事では、Serializable インターフェイスを実装したクラスを、シリアライズして保存する方法と、保存したオブジェクトを読み込み、再度メモリ上に復元するデシリアライズの方法を解説します。
シリアライズ(Serializable)
Java のオブジェクトをシリアライズ(Serializable)して、ディスク上に保存する方法から見ていきましょう。
Java のオブジェクトをシリアライズするには、対象のオブジェクトが java.io.Serializable インターフェイスを実装している必要があります。
今回は氏名、年齢の2つのプロパティを持った Person クラスを作成してシリアライズします。java.io.Serializable インターフェイスを忘れずに実装しておきましょう。
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
シリアライズ対象のクラスの準備ができたら、 ObjectOutputStream を使って Java オブジェクトを出力ストリームに書き出す処理を書いていきます。
このオブジェクトを出力ストリームに書き出す行為をシリアライズ(日本語では直列化)と呼びます。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.data"))) {
Person person = new Person("山田太郎", 20);
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
}
}
デシリアライズ
シリアライズ(Serializable)で出力したオブジェクトを、復元して再度メモリ上に展開するにはデシリアライズを行います。
では、先程シリアライズで出力した Person クラスのファイルを、ObjectInputStream に読み込んでデシリアライズしてみましょう。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
// デシリアライズ
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.data"))) {
Person person = (Person) ois.readObject();
System.out.println("name=" + person.getName());
System.out.println("age=" + String.valueOf(person.getAge()));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
■ 実行結果
name=山田太郎
age=20
このように、シリアライズによってファイルに保存した Java オブジェクトが、デシリアライズによって復元されています。
シリアライズについてもっと知る
Java でシリアライズ・デシリアライズをする基本的な方法を解説してきました。
ここからは、シリアライズに関するもう少し詳しい内容を確認していきましょう。
serialVersionUIDを付けよう
「serialVersionUID」とはクラスのバージョンのようなもので、java.io.Serializable インターフェイスを実装したシリアライズ可能なクラスに static 変数として次のように定義することが推奨されています。
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
・・・
}
この「serialVersionUID」は、クラスにプロパティを追加した時など、何かしら構造的な変更をした時にバージョンを上げいきます。
異なるserialVersionUIDのクラスはデシリアライズ不可
デシリアライズ時に「serialVersionUID」が異なるファイルを読み込もうとすると、java.io.InvalidClassException が発生します。
このように「serialVersionUID」をクラスに宣言することにより、異なるバージョンのクラスを読み込んでしまうことで発生しうる不正な動作を防ぐことができます。
「serialVersionUID」は必要?
「serialVersionUID」が定義されていないクラスでも、コンパイル時に警告は出るがシリアライズやデシリアライズをすることは可能です。
公式の JavaDoc には「serialVersionUID」について次のように書かれています。
直列化可能クラスがserialVersionUIDを明示的に宣言しない場合、直列化ランタイムは「Java(TM)オブジェクト直列化仕様」で説明されているように、クラスのさまざまな側面に基づいて、クラスのserialVersionUIDのデフォルト値を計算します。ただし、すべての直列化可能クラスがserialVersionUID値を明示的に宣言することを強くお薦めします。
Serializable (Java Platform SE 8)
つまり、省略した場合は自動で付与するが、明示的に「serialVersionUID」を定義することを推奨すると書かれています。
「serialVersionUID」を極力変えない工夫を
前述したように「serialVersionUID」を変更すると、過去に作成したファイルをデシリアライズする時にエラーとなります。
もちろん、大幅にクラスの内容を変えた場合はバージョンを上げて、古いバージョンのファイルをデシリアライズした時にエラーにすることで、想定外の動作を防ぐ必要があります。
しかし、影響が少ないプロパティを1つ追加する程度の変更であれば「serialVersionUID」は変更せず、新しプロパティが null になった場合などても問題なく動作するような作りにし、より汎用的な設計にしておいた方がよいでしょう。
まとめ
Java のオブジェクトをファイルなどに保存するシリアライズ(Serializable)と、それを復元するデシリアライズ処理について解説してきました。
シリアライズをすることで、オブジェクトのインスタンスの内容を別のサーバーにファイルで送信したりできるなど、さまざまな用途で使えるため是非おぼえておきましょう。
【関連記事】
▶【Java】クラス?メソッド?クラスメソッドって何?
java.io.Serializable インターフェイスは、メソッドがないマーカーインターフェイスであるため、このインターフェイスを実装してもオーバライドするメソッドなどはありません。
また、java.io.Serializableを実装していないクラスをシリアライズしようとすると、NotSerializableException が発生します。