1前書き

このチュートリアルでは、

javaの

java.io.Externalizable

interface

について簡単に説明します。このインタフェースの主な目的は、カスタムのシリアル化と逆シリアル化を容易にすることです。

先に進む前に、リンク:/java-serialization[Javaでのシリアル化]の記事をチェックしてください。次の章では、このインタフェースを使ってJavaオブジェクトをシリアル化する方法について説明します。

その後、

java.io.Serializable

インターフェースと比較した主な違いについて説明します。


2



Externalizable

インターフェイス


Externalizable

は、

java.io.Serializable

マーカーインタフェースから拡張されています。 **

Externalizable

インターフェースを実装するクラスはすべて、

writeExternal()



readExternal()

メソッドをオーバーライドする必要があります。そうすれば、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

を定義しました。


  • writeExternal()

    メソッドでは、オブジェクトのプロパティを

    ObjectOutput

    ストリームに追加しています** これには、

    String

    の場合は

    writeUTF()

    、int値の場合は

    writeInt()

    などの標準メソッドがあります。

次に、

オブジェクトを逆シリアル化するために、

readUTF()、readInt()

メソッドを使用して

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

にも適用できることを覚えておいてください。

継承階層のすべてのサブクラスにread/writeメソッドを実装するだけです。

前のセクションから

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つの追加プロパティを追加し、最初のプロパティをシリアル化しました。

  • 親クラスのフィールドも保存/復元するために、シリアライザメソッド内で

    super.writeExternal(out)、super.readExternal(in)

    も呼び出しました。

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

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
}


Region

クラスの

population

フィールドをシリアル化していないため、そのプロパティの値は__nullです。


3.



外部化



シリアル化


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

  • [#serialization__responsibility]#

    シリアライゼーションの責任

ここでの主な違いは、シリアル化プロセスの処理方法です。クラスが

java.io.Serializable

インタフェースを実装すると、JVMはクラスインスタンスのシリアル化に対して全責任を負います。 __Externalizableの場合は、シリアライゼーションとデシリアライゼーションのプロセス全体を担当するのはプログラマです。

  • [#use__case]#

    ユースケース

オブジェクト全体をシリアル化する必要がある場合は、

Serializable

インターフェイスが適しています。一方、


カスタムシリアル化の場合、

Externalizable

** を使用してプロセスを制御できます。

  • [#パフォーマンス]#

    パフォーマンス


java.io.Serializable

インタフェースはリフレクションとメタデータを使用するため、パフォーマンスが比較的低下します。それとは対照的に、**

Externalizable

インターフェースはシリアル化プロセスを完全に制御します。

  • [#reading__order]#

    読み上げ順序


  • Externalizable

    を使用している間は、すべてのフィールドの状態を記述どおりに正確な順序で読み取ることが必須です。それ以外の場合、例外が発生します。

たとえば、

Country

クラスの

code

プロパティと

name

プロパティの読み取り順序を変更すると、

java.io.EOFException

がスローされます。

一方、

Serializable

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

  • [#custom__serialization]#

    カスタムシリアライゼーション


transient

キーワードでフィールドをマークすることで、

Serializable

インターフェースを使用してカスタムシリアル化を実現できます。

JVMは特定のフィールドをシリアル化しませんが、デフォルト値を使用してフィールドをファイルストレージに追加します

。そのため、カスタムシリアル化の場合は

Externalizable

を使用することをお勧めします。


4結論


Externalizable

インターフェースのこの短いガイドでは、主な機能、利点、および簡単な使用例を説明しました。また、

Serializable

インターフェースとの比較も行いました。

いつも通り、チュートリアルの完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java[GitHubで利用可能]です。