1前書き

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

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

直列化プロセスはインスタンスに依存しません。つまり、オブジェクトはあるプラットフォームで直列化し、別のプラットフォームで直列化復元することができます。

直列化に適したクラスは特別なマーカーインタフェースを実装する必要があります


Serializable.


ObjectInputStream



ObjectOutputStream

はどちらも、それぞれ

java.io.InputStream



java.io.OutputStream

を拡張する高レベルクラスです。

ObjectOutputStream

は、オブジェクトのプリミティブ型とグラフをバイトのストリームとして

OutputStream

に書き込むことができます。これらのストリームは、後で

ObjectInputStream

を使用して読み取ることができます。


ObjectOutputStream

の最も重要なメソッドは次のとおりです。

public final void writeObject(Object o) throws IOException;

これは、直列化可能オブジェクトを受け取り、それをバイトのシーケンス(ストリーム)に変換します。同様に、

ObjectInputStream

で最も重要なメソッドは次のとおりです。

public final Object readObject()
  throws IOException, ClassNotFoundException;

これはバイトのストリームを読み込み、それをJavaオブジェクトに変換することができます。

これを元のオブジェクトにキャストバックすることができます。


Person

クラスを使用した直列化について説明しましょう。静的フィールドは(オブジェクトではなく)クラスに属し、直列化されていないことに注意してください。また、シリアライゼーション中にクラスフィールドを無視するためにキーワード

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を使用してロードされます。

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

ロードされたオブジェクトは

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

  • JVMはバージョン番号(

    long

    )を各直列化可能クラスに関連付けます** これは、保存されたオブジェクトとロードされたオブジェクトが同じ属性を持ち、したがって直列化で互換性があることを確認するために使用されます。

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

InvalidClassException

が発生する可能性があります。

直列化可能クラスが

serialVersionUIDを宣言しない場合、JVMは実行時に自動的にそれを生成します。ただし、生成されたクラスはコンパイラに依存し、予期しない

InvalidClassExceptions

が発生する可能性があるため、各クラスで

serialVersionUID__を宣言することを強くお勧めします。


3.3. Java

でのカスタムシリアル化

Javaは、オブジェクトをシリアル化できるデフォルトの方法を指定しています。 Javaクラスはこのデフォルトの動作をオーバーライドできます。カスタムシリアル化は、シリアル化できない属性を持つオブジェクトをシリアル化するときに特に便利です。これは、直列化したい2つのメソッドをクラス内に提供することによって実行できます。

private void writeObject(ObjectOutputStream out) throws IOException;

そして

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のシリアライゼーションについて概説し、留意すべき重要事項について説明し、カスタムシリアライゼーションの実行方法を示しました。

いつものように、このチュートリアルで使われているソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java/src/main/java/com/baeldung/serialization[GitHubで利用可能]です。