Javaシリアライゼーション入門

Javaシリアル化の概要

1. 前書き

シリアル化とは、オブジェクトの状態をバイトストリームに変換することです。逆シリアル化はその逆を行います。 別の言い方をすれば、シリアライゼーションとは、Javaオブジェクトをバイトの静的ストリーム(シーケンス)に変換し、それをデータベースに保存したり、ネットワーク経由で転送したりすることです。

2. シリアライゼーションとデシリアライゼーション

シリアル化プロセスはインスタンスに依存しません。つまり、 オブジェクトは1つのプラットフォームでシリアル化され、別のプラットフォームで逆シリアル化されます。 Classes that are eligible for serialization need to implement a special marker interfaceSerializable.

ObjectInputStreamObjectOutputStreamはどちらも、それぞれjava.io.InputStreamjava.io.OutputStreamを拡張する高レベルのクラスです。 ObjectOutputStreamは、オブジェクトのプリミティブ型とグラフをバイトのストリームとしてOutputStreamに書き込むことができます。 これらのストリームは、その後、ObjectInputStreamを使用して読み取ることができます。

ObjectOutputStreamで最も重要な方法は次のとおりです。

public final void writeObject(Object o) throws IOException;

シリアル化可能なオブジェクトを受け取り、それをバイトのシーケンス(ストリーム)に変換します。 同様に、ObjectInputStreamで最も重要な方法は次のとおりです。

public final Object readObject()
  throws IOException, ClassNotFoundException;

バイトのストリームを読み取り、それをJavaオブジェクトに戻すことができます。 これを元のオブジェクトにキャストできます。

Personクラスを使用したシリアル化について説明しましょう。 static fields belong to a class (as opposed to an object) and are not serializedに注意してください。 また、キーワードtransientを使用して、シリアル化中にクラスフィールドを無視できることに注意してください。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;

    // getters and setters
}

以下のテストは、タイプPersonのオブジェクトをローカルファイルに保存し、この値を次の場所に読み戻す例を示しています。

@Test
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() ()
  throws IOException, ClassNotFoundException {
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream
      = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(p2.getAge() == p.getAge());
    assertTrue(p2.getName().equals(p.getName()));
}

FileOutputStreamを使用して、このオブジェクトの状態をファイルに保存するためにObjectOutputStreamを使用しました。 ファイル“yourfile.txt”がプロジェクトディレクトリに作成されます。 次に、このファイルはFileInputStream.を使用してロードされます。ObjectInputStreamはこのストリームを取得し、p2という新しいオブジェクトに変換します。

最後に、ロードされたオブジェクトの状態をテストし、元のオブジェクトの状態と一致します。

ロードされたオブジェクトは、Personタイプに明示的にキャストする必要があることに注意してください。

3. Javaシリアル化の警告

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

3.1. 継承と構成

クラスがjava.io.Serializableインターフェースを実装すると、そのすべてのサブクラスもシリアライズ可能になります。 逆に、オブジェクトに別のオブジェクトへの参照がある場合、これらのオブジェクトはSerializableインターフェイスを個別に実装する必要があります。そうしないと、NotSerializableExceptionがスローされます。

public class Person implements Serializable {
    private int age;
    private String name;
    private Address country; // must be serializable too
}

シリアル化可能なオブジェクトのフィールドの1つがオブジェクトの配列で構成されている場合、これらのオブジェクトもすべてシリアル化可能である必要があります。そうでない場合、NotSerializableExceptionがスローされます。

3.2. シリアルバージョンUID

The JVM associates a version (long) number with each serializable class.保存されたオブジェクトとロードされたオブジェクトが同じ属性を持ち、したがってシリアル化で互換性があることを確認するために使用されます。

この番号は、ほとんどのIDEによって自動的に生成され、クラス名、その属性、および関連するアクセス修飾子に基づいています。 変更を加えると数値が異なり、InvalidClassExceptionが発生する可能性があります。

シリアライズ可能なクラスがserialVersionUIDを宣言していない場合、JVMは実行時に自動的に生成します。 ただし、生成されたクラスはコンパイラに依存するため、各クラスがそのserialVersionUIDを宣言することを強くお勧めします。これにより、予期しないInvalidClassExceptionsが発生する可能性があります。

3.3. Javaでのカスタムシリアル化

Javaは、オブジェクトをシリアル化するデフォルトの方法を指定します。 Javaクラスは、このデフォルトの動作をオーバーライドできます。 カスタムシリアル化は、いくつかの非シリアル化可能な属性を持つオブジェクトをシリアル化しようとする場合に特に役立ちます。 これは、シリアライズするクラス内に2つのメソッドを提供することで実行できます。

private void writeObject(ObjectOutputStream out) throws IOException;

and

private void readObject(ObjectInputStream in)
  throws IOException, ClassNotFoundException;

これらのメソッドを使用すると、これらの非シリアル化可能な属性を、シリアル化可能な他の形式にシリアル化できます。

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;

    // setters and getters

    private void writeObject(ObjectOutputStream oos)
      throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }

    private void readObject(ObjectInputStream ois)
      throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}
public class Address {
    private int houseNumber;

    // setters and getters
}

次の単体テストは、このカスタムシリアル化をテストします。

@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame()
  throws IOException, ClassNotFoundException {
    Person p = new Person();
    p.setAge(20);
    p.setName("Joe");

    Address a = new Address();
    a.setHouseNumber(1);

    Employee e = new Employee();
    e.setPerson(p);
    e.setAddress(a);

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile2.txt");
    ObjectOutputStream objectOutputStream
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(e);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream
      = new FileInputStream("yourfile2.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Employee e2 = (Employee) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(
      e2.getPerson().getAge() == e.getPerson().getAge());
    assertTrue(
      e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}

このコードでは、カスタムシリアル化を使用してAddressをシリアル化することにより、シリアル化できない属性を保存する方法を示します。 NotSerializableException.を回避するために、シリアル化できない属性をtransientとしてマークする必要があることに注意してください

4. 結論

このクイックチュートリアルでは、Javaシリアル化を確認し、留意すべき重要事項について説明し、カスタムシリアル化の方法を示しました。

いつものように、このチュートリアルで使用されるソースコードはover on GitHubで利用できます。