Javaにおける外部化可能インタフェースの手引き

Javaの外部化可能インターフェイスのガイド

1. 前書き

このチュートリアルでは、we’ll have a quick look at java’s java.io.Externalizable interface。 このインターフェイスの主な目的は、カスタムシリアル化と逆シリアル化を容易にすることです。

先に進む前に、必ずserialization in Javaの記事を確認してください。 次の章では、このインターフェースでJavaオブジェクトをシリアル化する方法について説明します。

その後、java.io.Serializableインターフェースと比較した主な違いについて説明します。

2. The Externalizableインターフェース

Externalizableは、java.io.Serializableマーカーインターフェイスから拡張されます。 Any class that implements Externalizable interface should override the writeExternal(), readExternal() methods。 このようにして、JVMのデフォルトのシリアル化動作を変更できます。

2.1. 直列化

この簡単な例を見てみましょう。

public class Country implements Externalizable {

    private static final long serialVersionUID = 1L;

    private String name;
    private int code;

    // getters, setters

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(code);
    }

    @Override
    public void readExternal(ObjectInput in)
      throws IOException, ClassNotFoundException {
        this.name = in.readUTF();
        this.code = in.readInt();
    }
}

ここでは、Externalizableインターフェースを実装し、上記の2つのメソッドを実装するクラスCountryを定義しました。

In the writeExternal() method, we’re adding the object’s properties to the ObjectOutput stream.これには、Stringの場合はwriteUTF()、int値の場合はwriteInt()などの標準メソッドがあります。

次に、readUTF(), readInt()メソッドを使用してfor deserializing the object, we’re reading from the ObjectInput streamが、書き込まれたのとまったく同じ順序でプロパティを読み取ります。

serialVersionUIDを手動で追加することをお勧めします。 これがない場合、JVMは自動的に追加します。

自動生成された数値はコンパイラに依存します。 これは、起こりそうもないInvalidClassExceptionを引き起こす可能性があることを意味します。

上記で実装した動作をテストしてみましょう。

@Test
public void whenSerializing_thenUseExternalizable()
  throws IOException, ClassNotFoundException {

    Country c = new Country();
    c.setCode(374);
    c.setName("Armenia");

    FileOutputStream fileOutputStream
     = new FileOutputStream(OUTPUT_FILE);
    ObjectOutputStream objectOutputStream
     = new ObjectOutputStream(fileOutputStream);
    c.writeExternal(objectOutputStream);

    objectOutputStream.flush();
    objectOutputStream.close();
    fileOutputStream.close();

    FileInputStream fileInputStream
     = new FileInputStream(OUTPUT_FILE);
    ObjectInputStream objectInputStream
     = new ObjectInputStream(fileInputStream);

    Country c2 = new Country();
    c2.readExternal(objectInputStream);

    objectInputStream.close();
    fileInputStream.close();

    assertTrue(c2.getCode() == c.getCode());
    assertTrue(c2.getName().equals(c.getName()));
}

この例では、最初にCountryオブジェクトを作成し、それをファイルに書き込みます。 次に、ファイルからオブジェクトを逆シリアル化し、値が正しいことを確認します。

印刷されたc2オブジェクトの出力:

Country{name='Armenia', code=374}

これは、オブジェクトの逆シリアル化に成功したことを示しています。

2.2. 継承

クラスがSerializableインターフェースから継承する場合、JVMはサブクラスからもすべてのフィールドを自動的に収集し、それらをシリアル化可能にします。

これはExternalizableにも適用できることに注意してください。 We just need to implement the read/write methods for every sub-class of the inheritance hierarchy.

前のセクションのCountryクラスを拡張する以下のRegionクラスを見てみましょう。

public class Region extends Country implements Externalizable {

    private static final long serialVersionUID = 1L;

    private String climate;
    private Double population;

    // getters, setters

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        super.writeExternal(out);
        out.writeUTF(climate);
    }

    @Override
    public void readExternal(ObjectInput in)
      throws IOException, ClassNotFoundException {

        super.readExternal(in);
        this.climate = in.readUTF();
    }
}

ここでは、2つの追加プロパティを追加し、最初のプロパティをシリアル化しました。

we also called super.writeExternal(out), super.readExternal(in) within serializer methods to save/restore the parent class fields as wellに注意してください。

次のデータを使用して単体テストを実行してみましょう。

Region r = new Region();
r.setCode(374);
r.setName("Armenia");
r.setClimate("Mediterranean");
r.setPopulation(120.000);

デシリアライズされたオブジェクトは次のとおりです。

Region{
  country='Country{
    name='Armenia',
    code=374}'
  climate='Mediterranean',
  population=null
}

since we didn’t serialize the population field in Region class, the value of that property is null.に注意してください

3. ExternalizableSerializable

2つのインターフェースの主な違いを見てみましょう。

  • シリアル化の責任

ここでの主な違いは、シリアル化プロセスの処理方法です。 クラスがjava.io.Serializableインターフェースを実装する場合、JVMはクラスインスタンスのシリアル化に全責任を負います。 In case of Externalizable, it’s the programmer who should take care of the whole serialization and also deserialization process.

  • 使用事例

オブジェクト全体をシリアル化する必要がある場合は、Serializableインターフェイスの方が適しています。 一方、for custom serialization, we can control the process using Externalizable

  • パフォーマンス

java.io.Serializableインターフェースはリフレクションとメタデータを使用するため、パフォーマンスが比較的遅くなります。 比較すると、Externalizable interface gives you full control over the serialization process.

  • 読書順序

While using Externalizable, it’s mandatory to read all the field states in the exact order as they were written.それ以外の場合は、例外が発生します。

たとえば、Countryクラスのcodeプロパティとnameプロパティの読み取り順序を変更すると、java.io.EOFExceptionがスローされます。

一方、Serializableインターフェースにはその要件はありません。

  • カスタムシリアル化

フィールドをtransientキーワードでマークすることにより、Serializableインターフェイスでカスタムシリアル化を実現できます。 The JVM won’t serialize the particular field but it’ll add up the field to file storage with the default value。 そのため、カスタムシリアル化の場合はExternalizableを使用することをお勧めします。

4. 結論

Externalizableインターフェースのこの短いガイドでは、主要な機能、利点について説明し、簡単な使用例を示しました。 また、Serializableインターフェースとの比較も行いました。

いつものように、チュートリアルの完全なソースコードはover on GitHubで入手できます。