1. 序章

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

2. シリアル化と逆シリアル化

シリアル化プロセスはインスタンスに依存しません。 たとえば、あるプラットフォームでオブジェクトをシリアル化し、別のプラットフォームでオブジェクトを逆シリアル化できます。 シリアル化の対象となるクラスは、特別なマーカーインターフェイスを実装する必要があります。 シリアル化可能。 

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;

This method can read a stream of bytes and convert it back into a Java object. It can then be cast back to the original object.

Let’s illustrate serialization with a Person class. Note that static fields belong to a class (as opposed to an object) and are not serialized. Also, note that we can use the keyword transient to ignore class fields during serialization:

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() == person.getAge());
    assertTrue(p2.getName().equals(person.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

JVMは、バージョン(長い)番号を各シリアル化可能なクラスに関連付けます。これを使用して、保存およびロードされたオブジェクトが同じ属性を持ち、したがってシリアル化で互換性があることを確認します。

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

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

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のシリアル化を確認し、注意事項について説明し、カスタムのシリアル化を行う方法を学びました。

いつものように、この記事で使用されているソースコードは、GitHubからで入手できます。