1. 概要

シリアル化は、オブジェクトをバイトのストリームに変換するプロセスです。 その後、そのオブジェクトをデータベースに保存したり、ネットワーク経由で転送したりできます。 一連のバイトからオブジェクトを抽出する反対の操作は、逆シリアル化です。 それらの主な目的は、オブジェクトの状態を保存して、必要なときにオブジェクトを再作成できるようにすることです。

このチュートリアルでは、 Javaオブジェクトのさまざまなシリアル化アプローチについて説明します。

最初に、シリアル化のためのJavaのネイティブAPIについて説明します。 次に、JSONおよびYAML形式をサポートするライブラリを調べて同じことを行います。 最後に、いくつかの言語間プロトコルを見ていきます。

2. サンプルエンティティクラス

このチュートリアル全体で使用する単純なエンティティを紹介することから始めましょう。

public class User {
    private int id;
    private String name;
    
    //getters and setters
}

次のセクションでは、最も広く使用されているシリアル化プロトコルについて説明します。 例を通して、それぞれの基本的な使用法を学びます。

3. Javaのネイティブシリアル化

Javaでのシリアル化は、複数のシステム間で効果的かつ迅速な通信を実現するのに役立ちます。 Javaは、オブジェクトをシリアル化するデフォルトの方法を指定します。 Javaクラスは、このデフォルトのシリアル化をオーバーライドして、オブジェクトをシリアル化する独自の方法を定義できます。

Javaネイティブシリアル化の利点は次のとおりです。

  • シンプルでありながら拡張可能なメカニズムです
  • シリアル化された形式でオブジェクトタイプと安全性プロパティを維持します
  • リモートオブジェクトの必要に応じてマーシャリングとアンマーシャリングをサポートするために拡張可能
  • これはネイティブJavaソリューションであるため、外部ライブラリは必要ありません

3.1. デフォルトのメカニズム

Java Object Serialization Specification に従って、 ObjectOutputStreamクラスのwriteObject()メソッドを使用してオブジェクトをシリアル化できます。 一方、 ObjectInputStreamクラスに属するreadObject()メソッドを使用して、逆シリアル化を実行できます。

Userクラスを使用した基本的なプロセスを説明します。

まず、クラスはシリアル化可能なインターフェイスを実装する必要があります。

public class User implements Serializable {
    //fields and methods
}

次に、serialVersionUID属性を追加する必要があります。

private static final long serialVersionUID = 1L;

それでは、Userオブジェクトを作成しましょう。

User user = new User();
user.setId(1);
user.setName("Mark");

データを保存するためのファイルパスを指定する必要があります。

String filePath = "src/test/resources/protocols/user.txt";

次に、Userオブジェクトをファイルにシリアル化します。

FileOutputStream fileOutputStream = new FileOutputStream(filePath);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(user);

ここでは、 ObjectOutputStream を使用して、 Userオブジェクトの状態を“ user.txt”ファイルに保存しました。

一方、同じファイルから User オブジェクトを読み取り、それを逆シリアル化することができます。

FileInputStream fileInputStream = new FileInputStream(filePath);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
User deserializedUser = (User) objectInputStream.readObject();

最後に、ロードされたオブジェクトの状態をテストできます。

assertEquals(1, deserializedUser.getId());
assertEquals("Mark", deserializedUser.getName());

これは、Javaオブジェクトをシリアル化するためのデフォルトの方法です。 次のセクションでは、同じことを行うカスタムの方法を見ていきます。

3.2. Externalizableインターフェイスを使用したカスタムシリアル化

カスタムシリアル化は、シリアル化できない属性を持つオブジェクトをシリアル化しようとするときに特に役立ちます。 これは、 Externalizable インターフェイスを実装することで実行できます。これには、次の2つの方法があります。

public void writeExternal(ObjectOutput out) throws IOException;

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

シリアル化するクラス内にこれら2つのメソッドを実装できます。 詳細な例は、外部化可能インターフェイスに関する記事にあります。

3.3. Javaシリアル化の警告

Javaでのネイティブシリアル化に関するいくつかの注意事項があります。

  • Serializableとマークされたオブジェクトのみを永続化できます。ObjectクラスはSerializable、を実装していないため、Javaのすべてのオブジェクトを自動的に永続化できるわけではありません
  • クラスがSerializableインターフェイスを実装すると、そのすべてのサブクラスもシリアル化可能になります。 ただし、オブジェクトに別のオブジェクトへの参照がある場合、これらのオブジェクトはSerializableインターフェイスを個別に実装する必要があります。そうしないと、NotSerializableExceptionがスローされます
  • バージョン管理を制御する場合は、serialVersionUID属性を指定する必要があります。 この属性は、保存およびロードされたオブジェクトに互換性があることを確認するために使用されます。 したがって、常に同じであることを確認する必要があります。そうしないと、InvalidClassExceptionがスローされます。
  • Javaシリアル化は、I/Oストリームを多用します。 i ストリームを閉じるのを忘れると、リソースリークが発生するため、読み取りまたは書き込み操作の直後にストリームを閉じる必要があります。 このようなリソースのリークを防ぐために、try-with-resourcesイディオムを使用できます。

4. Gsonライブラリ

GoogleのGsonは、JavaオブジェクトをJSON表現との間でシリアル化および逆シリアル化するために使用されるJavaライブラリです。

Gsonは、GitHubでホストされているオープンソースプロジェクトです。 一般に、JavaオブジェクトをJSONに、またはその逆に変換するための toJson()および fromJson()メソッドを提供します。

4.1. Mavenの依存関係

Gsonライブラリの依存関係を追加しましょう。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.7</version>
</dependency>

4.2. Gsonシリアル化

まず、Userオブジェクトを作成しましょう。

User user = new User();
user.setId(1);
user.setName("Mark");

次に、JSONデータを保存するためのファイルパスを指定する必要があります。

String filePath = "src/test/resources/protocols/gson_user.json";

次に、 GsonクラスのtoJson()メソッドを使用して、Userオブジェクトを「gson_user.json」ファイルにシリアル化します。 :

Writer writer = new FileWriter(filePath);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
gson.toJson(user, writer);

4.3. Gsonの逆シリアル化

GsonクラスのfromJson()メソッドを使用して、JSONデータを逆シリアル化できます。

JSONファイルを読み取り、データをUserオブジェクトに逆シリアル化してみましょう。

Gson gson = new GsonBuilder().setPrettyPrinting().create();
User deserializedUser = gson.fromJson(new FileReader(filePath), User.class);

最後に、逆シリアル化されたデータをテストできます。

assertEquals(1, deserializedUser.getId());
assertEquals("Mark", deserializedUser.getName());

4.4. Gsonの機能

Gsonには、次のような多くの重要な機能があります。

  • コレクション、ジェネリック型、ネストされたクラスを処理できます
  • Gsonを使用すると、カスタムシリアライザーやデシリアライザーを作成して、プロセス全体を制御することもできます。
  • 最も重要なことは、ソースコードにアクセスできないクラスのインスタンスを逆シリアル化できることです
  • さらに、クラスファイルが異なるバージョンで変更された場合に備えて、バージョン管理機能を使用できます。 新しく追加されたフィールドに@Sinceアノテーションを使用してから、GsonBuilder のsetVersion()メソッドを使用できます。

その他の例については、 GsonSerializationおよびGsonDeserializationのクックブックを確認してください。

このセクションでは、GsonAPIを使用してJSON形式でデータをシリアル化しました。 次のセクションでは、JacksonAPIを使用して同じことを行います。

5. ジャクソンAPI

Jackson は、「JavaJSONライブラリ」または「Javaに最適なJSONパーサー」とも呼ばれます。 JSONデータを操作するための複数のアプローチを提供します。

ジャクソンライブラリ全般を理解するには、ジャクソンチュートリアルから始めるのがよいでしょう。

5.1. Mavenの依存関係

Jacksonライブラリの依存関係を追加しましょう。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.12.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.12.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
     <version>2.12.4</version>
</dependency>

5.2. JavaオブジェクトからJSONへ

ObjectMapperクラスに属するwriteValue()メソッドを使用して、任意のJavaオブジェクトをJSON出力としてシリアル化できます。

Userオブジェクトを作成することから始めましょう。

User user = new User();
user.setId(1);
user.setName("Mark Jonson");

その後、JSONデータを保存するためのファイルパスを指定しましょう。

String filePath = "src/test/resources/protocols/jackson_user.json";

これで、 ObjectMapper クラスを使用して、UserオブジェクトをJSONファイルに保存できます。

File file = new File(filePath);
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(file, user);

このコードは、データを「jackson_user.json」ファイルに書き込みます。

5.3. JSONからJavaオブジェクトへ

ObjectMapperの単純なreadValue()メソッドは優れたエントリポイントです。これを使用して、JSONコンテンツをJavaオブジェクトに逆シリアル化できます。

JSONファイルからUserオブジェクトを読み取ってみましょう。

User deserializedUser = mapper.readValue(new File(filePath), User.class);

ロードされたデータはいつでもテストできます。

assertEquals(1, deserializedUser.getId());
assertEquals("Mark Jonson", deserializedUser.getName());

5.4. ジャクソンの特徴

  • Jacksonは、Java用の堅実で成熟したJSONシリアル化ライブラリです。
  • ObjectMapper クラスは、シリアル化プロセスのエントリポイントであり、JSONオブジェクトを非常に柔軟に解析および生成するための簡単な方法を提供します。
  • Jacksonライブラリの最大の強みの1つは、高度にカスタマイズ可能なシリアル化および逆シリアル化プロセスです。

これまで、JSON形式でのデータのシリアル化を見てきました。 次のセクションでは、YAMLを使用したシリアル化について説明します。

6. YAML

YAMLは「YAMLAin’tMarkupLanguage」の略です。 これは、人間が読める形式のデータシリアル化言語です。 YAMLは、構成ファイルだけでなく、データを保存または送信するアプリケーションでも使用できます。

前のセクションでは、JacksonAPIがJSONファイルを処理するのを見ました。 JacksonAPIを使用してYAMLファイルを処理することもできます。 詳細な例は、YAMLとJacksonの解析に関する記事にあります。

それでは、他のライブラリを見てみましょう。

6.1. YAML Beans

YAML Beans を使用すると、JavaオブジェクトグラフをYAMLとの間で簡単にシリアル化および逆シリアル化できます。

YamlWriter クラスは、JavaオブジェクトをYAMLにシリアル化するために使用されます。 write()メソッドは、パブリックフィールドとbeanのgetterメソッドを認識することにより、これを自動的に処理します。

逆に、 YamlReader クラスを使用して、YAMLをJavaオブジェクトに逆シリアル化できます。 read()メソッドはYAMLドキュメントを読み取り、それを必要なオブジェクトに逆シリアル化します。

まず、 YAMLBeansの依存関係を追加しましょう。

<dependency>
    <groupId>com.esotericsoftware.yamlbeans</groupId>
    <artifactId>yamlbeans</artifactId>
    <version>1.15</version>
</dependency>

今。 Userオブジェクトのマップを作成しましょう。

private Map<String, User> populateUserMap() {
    User user1 = new User();
    user1.setId(1);
    user1.setName("Mark Jonson");
    //.. more user objects
    
    Map<String, User> users = new LinkedHashMap<>();
    users.put("User1", user1);
    // add more user objects to map
    
    return users;
}

その後、データを保存するためのファイルパスを指定する必要があります。

String filePath = "src/test/resources/protocols/yamlbeans_users.yaml";

これで、 YamlWriter クラスを使用して、マップをYAMLファイルにシリアル化できます。

YamlWriter writer = new YamlWriter(new FileWriter(filePath));
writer.write(populateUserMap());
writer.close();

反対側では、YamlReaderクラスを使用してマップを逆シリアル化できます。

YamlReader reader = new YamlReader(new FileReader(filePath));
Object object = reader.read();
assertTrue(object instanceof Map); 

最後に、ロードされたマップをテストできます。

Map<String, User> deserializedUsers = (Map<String, User>) object;
assertEquals(4, deserializedUsers.size());
assertEquals("Mark Jonson", (deserializedUsers.get("User1").getName()));
assertEquals(1, (deserializedUsers.get("User1").getId()));

6.2. SnakeYAML

SnakeYAML は、JavaオブジェクトをYAMLドキュメントに、またはその逆にシリアル化するための高レベルAPIを提供します。 最新バージョンの1.2は、JDK1.8以降のJavaバージョンで使用できます。 String List MapなどのJava構造を解析できます。

SnakeYAMLのエントリポイントはYamlクラスであり、シリアル化と逆シリアル化に役立ついくつかのメソッドが含まれています。

YAML入力をJavaオブジェクトに逆シリアル化するために、 load()メソッドを使用して単一のドキュメントをロードし、 loadAll()メソッドを使用して複数のドキュメントをロードできます。 これらのメソッドは、InputStreamおよびStringオブジェクトを受け入れます。

逆に、 dump()メソッドを使用して、JavaオブジェクトをYAMLドキュメントにシリアル化できます。

詳細な例は、YAMLとSnakeYAMLの解析に関する記事にあります。

当然、SnakeYAMLはJava Map でうまく機能しますが、カスタムJavaオブジェクトでも機能します。

このセクションでは、データをYAML形式にシリアル化するためのさまざまなライブラリについて説明しました。 次のセクションでは、クロスプラットフォームプロトコルについて説明します。

7. ApacheThrift

Apache Thrift は元々Facebookによって開発され、現在はApacheによって保守されています。

Thriftを使用する最大の利点は、がより低いオーバーヘッドで言語間のシリアル化をサポートすることです。 また、多くのシリアル化フレームワークは1つのシリアル化形式のみをサポートしますが、ApacheThriftでは複数の形式から選択できます。

7.1. 節約機能

Thriftは、プロトコルと呼ばれるプラグ可能なシリアライザーを提供します。 これらのプロトコルは、データ交換にいくつかのシリアル化形式のいずれかを使用する柔軟性を提供します。 サポートされているプロトコルの例は次のとおりです。

  • TBinaryProtocol はバイナリ形式を使用しているため、テキストプロトコルよりも処理が高速です。
  • TCompactProtocol は、よりコンパクトなバイナリ形式であるため、処理もより効率的です。
  • TJSONProtocolはデータのエンコードにJSONを使用します

Thriftは、コンテナタイプ(リスト、セット、マップ)のシリアル化もサポートしています。

7.2. Mavenの依存関係

アプリケーションでApacheThriftフレームワークを使用するには、Thriftライブラリを追加しましょう。

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.14.2</version>
</dependency>

7.3. スリフトデータのシリアル化

Apache Thriftプロトコルとトランスポートは、レイヤードスタックとして連携するように設計されています。 プロトコルはデータをバイトストリームにシリアル化し、トランスポートはバイトの読み取りと書き込みを行います。

前に述べたように、Thriftは多くのプロトコルを提供します。 バイナリプロトコルを使用した節約シリアル化について説明します。

まず、Userオブジェクトが必要です。

User user = new User();
user.setId(2);
user.setName("Greg");

次のステップは、バイナリプロトコルを作成することです。

TMemoryBuffer trans = new TMemoryBuffer(4096);
TProtocol proto = new TBinaryProtocol(trans);

それでは、データをシリアル化しましょう私たちはを使用してそうすることができます書きます API:

proto.writeI32(user.getId());
proto.writeString(user.getName());

7.4. スリフトデータの逆シリアル化

readAPIを使用してデータを逆シリアル化します。

int userId = proto.readI32();
String userName = proto.readString();

最後に、ロードされたデータをテストできます。

assertEquals(2, userId);
assertEquals("Greg", userName);

その他の例は、 ApacheThriftに関する記事にあります。

8. Googleプロトコルバッファ

このチュートリアルで取り上げる最後のアプローチは、 Google Protocol Buffers (protobuf)です。 これはよく知られているバイナリデータ形式です。

8.1. プロトコルバッファの利点

プロトコルバッファには、次のようないくつかの利点があります。

  • それは言語とプラットフォームに中立です
  • これはバイナリ転送形式であり、データがバイナリとして送信されることを意味します。 これにより、必要なスペースと帯域幅が少なくなるため、伝送速度が向上します。
  • 新しいバージョンが古いデータを読み取ることができるように、またその逆も可能になるように、下位互換性と上位互換性の両方をサポートします

8.2. Mavenの依存関係

Googleプロトコルバッファライブラリの依存関係を追加することから始めましょう。

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.17.3</version>
</dependency>

8.3. プロトコルの定義

依存関係が二乗されたので、メッセージ形式を定義できます。

syntax = "proto3";
package protobuf;
option java_package = "com.baeldung.serialization.protocols";
option java_outer_classname = "UserProtos";
message User {
    int32 id = 1;
    string name = 2;
}

これは、 User typeの単純なメッセージのプロトコルであり、idname、タイプintegerの2つのフィールドがあります。 ]stringそれぞれ。 「user.proto」ファイルとして保存していることに注意してください。

8.4. ProtobufファイルからのJavaコードの生成

protobufファイルを取得したら、protocコンパイラを使用してそこからコードを生成できます。

protoc -I=. --java_out=. user.proto

その結果、このコマンドはUserProtos.javaファイルを生成します。

その後、UserProtosクラスのインスタンスを作成できます。

UserProtos.User user = UserProtos.User.newBuilder().setId(1234).setName("John Doe").build();

8.5. Protobufのシリアル化と逆シリアル化

まず、データを保存するためのファイルパスを指定する必要があります。

String filePath = "src/test/resources/protocols/usersproto";

それでは、データをファイルに保存しましょう。 UserProtos クラス(protobufファイルから生成したクラス)の writeTo()メソッドを使用できます。

FileOutputStream fos = new FileOutputStream(filePath);
user.writeTo(fos);

このコードを実行した後、オブジェクトはバイナリ形式にシリアル化され、「usersproto」ファイルに保存されます。

反対に、 mergeFrom()メソッドを使用して、ファイルからそのデータをロードし、ユーザーオブジェクトに逆シリアル化することができます。

UserProtos.User deserializedUser = UserProtos.User.newBuilder().mergeFrom(new FileInputStream(filePath)).build();

最後に、ロードされたデータをテストできます。

assertEquals(1234, deserializedUser.getId());
assertEquals("John Doe", deserializedUser.getName());

9. 概要

このチュートリアルでは、Javaオブジェクトのシリアル化に広く使用されているプロトコルについて説明しました。 アプリケーションのデータシリアル化形式の選択は、データの複雑さ、人間の可読性の必要性、速度などのさまざまな要因によって異なります。

Javaは、使いやすい組み込みのシリアル化をサポートしています。

読みやすさとスキーマがないため、JSONの方が適しています。 したがって、GsonとJacksonの両方がJSONデータをシリアル化するための優れたオプションです。 それらは使いやすく、十分に文書化されています。 データの編集にはYAMLが最適です。

一方、バイナリ形式はテキスト形式よりも高速です。アプリケーションで速度が重要な場合、ApacheThriftとGoogleProtocolBuffersはデータのシリアル化に最適です。どちらもXMLやJSONよりもコンパクトで高速です。フォーマット。

要約すると、利便性とパフォーマンスの間にはトレードオフが存在することが多く、シリアル化にも違いはありません。 もちろん、データのシリアル化に使用できる形式は他にもたくさんあります。

いつものように、完全なサンプルコードはGitHuboverです。