1. Java におけるバイナリシリアライズ
バイナリシリアライズは、オブジェクトをできるだけコンパクトかつ高速にバイト列へ変換するための Java 標準の仕組みです。これには ObjectOutputStream と ObjectInputStream のクラスを使用します。生成されるファイルは、人間が読むことを想定していないバイトの集合です。
「バイナリ」と呼ばれるのは、すべてが「生の」形でシリアライズされるからです。数値、文字列、配列、さらにはオブジェクト間の参照までもがバイトに変換されます。これはぎゅっと詰め込まれたスーツケースのようなもので、効率的で高速ですが、説明書がないと中身の場所は直感的に分かりません。
Java ではどのように動作しますか?
たとえば、次のようなクラス User があります。
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
// コンストラクタ、ゲッターとセッター
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
バイナリファイルへのシリアライズ
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
User user = new User("Vasya", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
System.out.println("User オブジェクトを user.bin にシリアライズしました");
} catch (Exception e) {
e.printStackTrace();
}
バイナリファイルからのデシリアライズ
import java.io.FileInputStream;
import java.io.ObjectInputStream;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
System.out.println("ファイルから読み取りました: " + loadedUser.getName() + ", " + loadedUser.getAge());
} catch (Exception e) {
e.printStackTrace();
}
注意: ファイル user.bin をテキストエディタで開くと、¬í sr ... のように見えることがあります — これは正常で、仕様どおりです!
バイナリシリアライズの利点
- コンパクトさと速度。 余計な「装飾」なしで、保存と読み込みを最大限高速に行えます。
- オブジェクトのすべてのフィールドを保持できます(入れ子のオブジェクトも、それらが Serializable を実装していれば可)。
- 内部キャッシュや Java プログラム間での受け渡しに手軽に使えます。
欠点
- 可読性がない。 人間が中身を見ても何が何だか分かりません。
- クラスのバージョンに強く依存。 構造(フィールドの追加/削除)を変えると、古いファイルの読み取りが壊れる可能性があります。
- 異なる Java や JVM のバージョン間で互換性の問題が起きることがあります。
- 他言語とのデータ交換には不向きです。
- セキュリティ: 未検証のソースから受け取ったデータをデシリアライズするのは、脆弱性に直結します。
2. テキスト形式のシリアライズ:JSON、XML など
バイナリシリアライズは内部用途には適していますが、しばしば異なる言語(Java、JavaScript、Python)間でデータを渡したり、設定・ログ・API 用に読みやすい形で保存したりする必要があります。そのために用いられるのがテキスト形式、すなわち JSON、XML、YAML、CSV などです。
JSON — 最も一般的
JSON (JavaScript Object Notation) はコンパクトで可読性の高い形式です。シリアライズされた User オブジェクトの例:
{
"name": "Vasya",
"age": 30
}
Java で JSON を扱う際によく使われるライブラリには、最も一般的な Jackson のほか、Gson、Moshi、JSON-B などがあります。
XML — 昔ながらの相棒
XML (Extensible Markup Language) はより「冗長」ですが、定形式で厳格です。
<User>
<name>Vasya</name>
<age>30</age>
</User>
Java で XML を扱う際は、標準ライブラリの JAXB(あるいは、より古い XStream)がよく用いられます。
YAML、CSV など
- YAML — JSON に似ていますが、より簡潔です。複雑なオブジェクトのシリアライズよりも、設定ファイルで使われることが多いです。
- CSV — 「平坦な」表形式には適していますが、入れ子構造には向きません。
- ほかにもさまざまな形式がありますが、Java では多くの場合 JSON と XML が使われます。
3. 形式の比較:いつ何を使うべきか?
| 形式 | 可読性 | コンパクトさ | 速度 | 互換性 | 使用場面 |
|---|---|---|---|---|---|
| バイナリ | いいえ | ++ | ++ | Java のみ | 内部キャッシュ、JVM 間での高速な保存 |
| JSON | はい | + | + | あらゆる言語 | REST API、外部サービス連携、設定 |
| XML | はい | - | - | あらゆる言語 | 統合、厳密なスキーマ、レガシーシステム |
- バイナリ — 外部システムとのやり取りが不要で、最大限の性能が重要な内部用途に選びましょう。
- JSON — Web アプリ、モバイルクライアント、REST API との連携や設定の保存に最適です。
- XML — 厳格なスキーマが必要な場合や「エンタープライズ」統合で有用です。
重要! バイナリシリアライズは Java プログラム間でのデータ受け渡しにのみ適しており、その場合でも同一バージョン同士で使う方が安全です。JSON や XML のようなテキスト形式はより汎用的で、異なる言語やプラットフォーム間のデータ交換に適し、情報を読みやすく移植性の高い形にします。
4. 実践:バイナリ形式とテキスト形式へのシリアライズ
バイナリシリアライズ(ObjectOutputStream/ObjectInputStream)
上でも見たとおりですが、復習しておきましょう。
// シリアライズ
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
// デシリアライズ
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
}
Jackson を使った JSON へのシリアライズ(簡単に)
Jackson を使うにはライブラリをプロジェクトに追加する必要があります。Maven や Gradle はこの後学びますが、当面は JAR ファイルを手動で追加しても構いません。Maven の依存関係例:
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
シリアライズ/デシリアライズの例:
import com.fasterxml.jackson.databind.ObjectMapper;
User user = new User("Vasya", 30);
ObjectMapper mapper = new ObjectMapper();
try {
// 文字列へシリアライズ
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Vasya","age":30}
// ファイルへシリアライズ
mapper.writeValue(new File("user.json"), user);
// 文字列からのデシリアライズ
User loadedUser = mapper.readValue(json, User.class);
// ファイルからのデシリアライズ
User loadedFromFile = mapper.readValue(new File("user.json"), User.class);
} catch (Exception e) {
e.printStackTrace();
}
JSON ファイルは任意のテキストエディタで開けるため、データは読みやすく、さまざまなアプリや言語間で容易にやり取りできます。
5. どの形式をいつ使うか:実践的なアドバイス
- 内部キャッシュ、一時ファイル、Java プログラム間での高速な読み書き: 標準のバイナリシリアライズを使用。ただしバージョン互換性に注意。
- 外部サービスとの連携、設定の保存、フロントエンドとの統合: JSON(Jackson、Gson)。
- 「エンタープライズ」システムとの統合で厳密なスキーマが必要: XML(JAXB)。
- 人がファイルを開いて読める必要がある: JSON または XML(バイナリ形式は不可)。
6. シリアライズ形式でよくあるミス
エラー No. 1: シリアライズ不能なフィールドを含むオブジェクトをシリアライズしようとする。 クラスに Serializable を実装していないフィールド(たとえばストリームやデータベース接続)がある場合、バイナリシリアライズはエラーになります。JSON では致命的ではありませんが、「非標準」な型では問題が起きることがあります。
エラー No. 2: バイナリファイルをテキストエディタで開いて驚く。 これは正常です! バイナリファイルは人間が読むことを目的としていません。
エラー No. 3: クラス構造を変更したら、古いバイナリファイルが読めなくなる。 バイナリシリアライズはクラス構造の変更に敏感で、しばしば InvalidClassException が発生します。JSON/XML ではこの問題は小さく、未知のフィールドは無視されたり既定値が入ったりします。
エラー No. 4: 外部システムとの連携にバイナリシリアライズを使う。 これはうまくいきません。バイナリ形式を理解できるのは基本的に Java(しかもバージョンが一致している場合)だけです。
エラー No. 5: JSON/XML で必要なアノテーションを付け忘れる。 ライブラリによっては @JsonProperty、@XmlElement のようなアノテーションが必要で、ないとシリアライズ/デシリアライズが期待どおり動かないことがあります。
エラー No. 6: すべての入れ子オブジェクトがシリアライズ可能か確認しない。 バイナリシリアライズでは頻出の問題です。モデルに複雑な型が含まれる場合は、JSON でも問題になることがあります。
GO TO FULL VERSION