Java - シリアライズ、デシリアライズ

Javaでは、シリアライゼーションはオブジェクトをbyteに変換することを意味します。 byteに変換すると、現在のオブジェクトの状態のままファイルに保存したり(キャッシュ)、ネットワーク経由で他のシステムに渡すことができます。

デシリアライズとは、シリアライズされたバイトデータをオブジェクトに戻すことです。

シリアライゼーションが使用される例では、Javaプログラムはセッション情報をオブジェクトとして持っていますが、プログラムが再起動されたときに以前に持っていたセッション情報を維持したいと思います。これを実装するために、最初の方法はセッションオブジェクト内のすべてのデータをJSONと同じ形式のテキストに変換し、それをファイルに保存できます。プログラムが再起動したら、以前に保存したファイルからデータを読み込み、そのデータでオブジェクトを再生成できます。

2番目の方法は、Javaオブジェクトをシリアル化してファイルに保存し、プログラムを再起動すると逆シリアル化してJavaオブジェクトに変換することです。好みに応じて実装すればよいのですが、Javaでのみ動作するプログラムなら直列化を利用するのが簡単かもしれません。

1. オブジェクトを直列化(Serialize)する部屋

java.io.Serializableを実装するクラスだけを直列化できます。どのオブジェクトを直列化する必要がある場合は、そのクラスがSerializableを実装していることを確認する必要があります。

以下の Student クラスは Serializable を実装するため、直列化できます。 Serializableを実装するときは、単に implements Serializableを入力するだけです。 抽象メソッドをオーバーライドして実装することはありません。

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student { name: " + name + ", age: " + age + " }";
    }
}

注意すべき点は、Serializableを実装するクラスは(基本データ型を除く)サブ変数のクラスはすべてSerializableを実装する必要があります。 たとえば、Student は子に Address クラスの変数を持っていますが、Address が Serializable を実装しないと直列化は失敗します。

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;
    public Address;  // need to implement 'Serializable'

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

直列化を試してみると、以下のように NotSerializableException がスローされます。

> Task :Example.main() FAILED
Exception in thread "main" java.io.NotSerializableException: Address
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1197)

2. オブジェクトを直列化してファイルに保存

ObjectOutputStream.writeObject(object) メソッドは、 Serializable を実装するクラスのオブジェクトオブジェクトを直列化します。 直列化した byte をファイルに保存するには、FileOutputStream と一緒に使用すればよい。

以下の例は、上記で紹介した Studentクラスのオブジェクトs1とs2を直列化してファイルとして保存する例です。

import java.io.*;

public class Example {

    public static void main(String[] args) throws IOException {
        Student s1 = new Student("John", 33);
        Student s2 = new Student("Doe", 30);

        FileOutputStream fileOutputStream
                = new FileOutputStream("/tmp/output");
        ObjectOutputStream objectOutputStream
                = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(s1);
        objectOutputStream.writeObject(s2);
        objectOutputStream.close();
    }
}

/tmp/output ファイルを確認すると、以下のようにバイトで保存されます。

aced 0005 7372 0007 5374 7564 656e 7408
0519 b243 26d1 0f02 0002 4900 0361 6765
4c00 046e 616d 6574 0012 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 7870 0000
0021 7400 044a 6f68 6e73 7100 7e00 0000
0000 2174 0003 446f 65

3. 逆シリアル化してオブジェクトに変える

ObjectInputStream.readObject() は byte を読み込み、Java オブジェクトに逆シリアル化します。直列化されたファイルを読み込んで逆シリアル化するときは、 FileInputStream と一緒に使うだけです。

以下の例は、上記で保存した直列化ファイル /tmp/output のバイトを逆シリアル化して Java オブジェクトに変換するコードです。 デシリアライズするときは、シリアライズしたオブジェクトの順序でデシリアライズする必要があります。さまざまなクラスを直列化したとき、逆シリアル化順序が異なると、逆シリアル化に失敗します。

import java.io.*;

public class Example1 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream
                = new FileInputStream("/tmp/output");
        ObjectInputStream objectInputStream
                = new ObjectInputStream(fileInputStream);
        Student s1 = (Student) objectInputStream.readObject();
        Student s2 = (Student) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

Output:

Student { name: John, age: 33 }
Student { name: Doe, age: 30 }

4. transientでメンバー変数をシリアライゼーションターゲットから除外する

Serializableを実装するクラスは、子にすべてのクラスがSerializableを実装しなければならず、そうでなければ直列化過程でエラーが発生すると言いました。

ただし、シリアライゼーションを必要としないメンバ変数は、クラス宣言にtransientを使用してシリアライゼーションターゲットから除外できます。 これにより、変数は直列化されず、Serializableを実装する必要がなくなります。

次の例では、 Address は transient として宣言されており、シリアライズされていません。

import java.io.Serializable;

public class Student implements Serializable {
    public String name;
    public int age;
    public transient Address address = new Address("New York");

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
codechachaCopyright ©2019 codechacha