1. はじめに
Java のシリアライズは、明示的に許可したオブジェクトに対してのみ機能します。そのためには、クラスが特別なインターフェース java.io.Serializable を実装している必要があります。
import java.io.Serializable;
public class Person implements Serializable {
// フィールド、コンストラクタ、メソッド
}
Serializable はマーカーインターフェースです。メソッドはなく、JVM に「このクラスはシリアライズして大丈夫です」と伝えるだけです。 Serializable を実装していないクラスのオブジェクトをシリアライズしようとすると、NotSerializableException が発生します。フィールド(または入れ子のオブジェクト)のうち少なくとも1つでもシリアライズ不可能であれば、シリアライズは失敗します。
ObjectOutputStream と ObjectInputStream
- ObjectOutputStream — オブジェクトをストリーム(例: ファイルやネットワーク)に書き出すクラス。
- ObjectInputStream — ストリームからオブジェクトを読み込むクラス。
これらはペアで動作します。一方がオブジェクトをシリアライズし、もう一方がデシリアライズします。
主要なメソッド
- writeObject(Object obj) — オブジェクトをシリアライズしてストリームに書き込みます。
- readObject() — ストリームからオブジェクトを読み込み、デシリアライズして返します。
重要: どちらのクラスも通常の入出力ストリーム(OutputStream と InputStream)の上で動作します。最もよく使われるのはファイルストリームの FileOutputStream と FileInputStream ですが、ネットワークストリームでも利用できます。
2. シリアライズの例
簡単な例を書いてみましょう。Person クラスのオブジェクトをシリアライズし、デシリアライズします。
ステップ1. クラスの定義
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// transient フィールドはシリアライズされない
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
ステップ2. オブジェクトをファイルへシリアライズ
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// ファイルへ書き込むためのストリームを作成する
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// オブジェクトを保存する
out.writeObject(person);
// ストリームを閉じる
out.close();
fileOut.close();
System.out.println("オブジェクトをファイル person.bin にシリアライズしました");
}
}
ステップ3. ファイルからオブジェクトをデシリアライズ
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// ファイルから読み込むためのストリームを開く
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// オブジェクトを復元する
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("オブジェクトをデシリアライズしました: " + person);
}
}
期待される出力
オブジェクトをファイル person.bin にシリアライズしました
オブジェクトをデシリアライズしました: Person{name='Alice', age=30, secret='null'}
注意! transient フィールドはシリアライズされません。デシリアライズ後は null になります。一時的なデータや機密データでは重要な性質です。
3. 制約と注意点
すべてのフィールドがシリアライズ可能であること
クラスに、Serializable を実装していないフィールド(またはそのようなオブジェクトを保持するフィールド)がある場合、シリアライズはエラーで終了します。例えば、Thread や Socket のような型は「そのままでは」シリアライズ可能にできません。
static と transient フィールド
- 静的フィールド(static)はシリアライズされません。これらはオブジェクトではなくクラスに属するためです。
- transient フィールド — transient としてマークされたものは明示的にシリアライズから除外され、復元後はデフォルト値(null、0 など)を受け取ります。
例外
- Serializable を実装していないオブジェクトをシリアライズしようとすると、NotSerializableException が発生します。
- デシリアライズ時には、ファイル未検出、クラス不一致、データ破損などのエラーが起こり得ます。
クラスのバージョン
シリアライズ後にクラスの構造(たとえばフィールドの追加/削除)を変更すると、デシリアライズ時に InvalidClassException が発生することがあります。バージョン管理には特別なフィールド serialVersionUID を使用します(詳細は別の講義で扱います)。
4. 実践: オブジェクトをファイルにシリアライズ/デシリアライズ
Person クラスがあり、人のリストをファイルに保存して後で読み戻したいとします。
Person クラス(シリアライズ可能)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // シリアライズされない
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
人リストのシリアライズ
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// リストを保存する
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("人のリストをシリアライズしました。");
}
}
人リストのデシリアライズ
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// リストを復元する
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
結果:
Alice (30), secret: null
Bob (25), secret: null
5. ありがちなミス
エラー No.1: クラスが Serializable を実装していない。 implements Serializable を追加し忘れると、シリアライズ時に NotSerializableException が発生します。最も一般的で単純なミスです。
エラー No.2: シリアライズ不可能なフィールド。 オブジェクトにシリアライズ不可能なフィールドが含まれている場合(例: Thread、Socket、あるいは Serializable を持たない任意の型)、シリアライズは失敗します。そのようなフィールドは transient を付けるか、シリアライズ可能にしてください。
エラー No.3: クラス構造の変更。 オブジェクトをシリアライズした後でクラスを変更(フィールドの追加/削除)すると、読み込み時に InvalidClassException が発生する可能性があります。バージョンの安定した管理のために serialVersionUID を指定しましょう。
エラー No.4: static フィールドをシリアライズしようとする。 static 修飾子の付いたフィールドはシリアライズされません。デシリアライズ後の値は、シリアライズ時ではなくクラスに定義されたデフォルトの値になります。
エラー No.5: ストリームを閉じていない。 処理後にストリームを閉じないと、ファイル破損やリソースリークの原因になります。try-with-resources を使うか、明示的にストリームを閉じてください。
エラー No.6: クラス不一致。 クラス名の変更や別パッケージへの移動が行われると、デシリアライズは機能しません。ストリームに保存されたクラスの名前とパッケージが完全に一致している必要があります。
GO TO FULL VERSION