1. 序章

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

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

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

2. The 外部化可能インターフェース

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ストリームに追加しています。これには、 StringwriteUTF()などの標準メソッドがあります。 int値の場合は]writeInt()

次に、オブジェクトを逆シリアル化するために、 readUTF()、readInt()メソッドを使用してObjectInputストリームから読み取り、プロパティを同じ順序で読み取ります。書かれています。

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にも適用できることに注意してください。 継承階層のすべてのサブクラスに読み取り/書き込みメソッドを実装する必要があります。

以下のRegionクラスを見てみましょう。これは、前のセクションのCountryクラスを拡張したものです。

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つのインターフェースの主な違いを見てみましょう。

  • シリアル化の責任

ここでの主な違いは、シリアル化プロセスの処理方法です。 クラスがjava.io.Serializableインターフェースを実装する場合、JVMはクラスインスタンスのシリアル化に全責任を負います。 Externalizableの場合、シリアル化と逆シリアル化のプロセス全体を処理するのはプログラマーです。

  • 使用事例

オブジェクト全体をシリアル化する必要がある場合は、Serializableインターフェイスの方が適しています。 一方、カスタムシリアル化の場合は、Externalizableを使用してプロセスを制御できます。

  • パフォーマンス

java.io.Serializable インターフェースは、リフレクションとメタデータを使用するため、パフォーマンスが比較的低下します。 比較すると、外部化可能なインターフェイスを使用すると、シリアル化プロセスを完全に制御できます。

  • 読書順序

Externalizableを使用している間は、すべてのフィールド状態を書き込まれたとおりの順序で読み取る必要があります。それ以外の場合は、例外が発生します。

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

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

  • カスタムシリアル化

フィールドにtransientキーワードをマークすることにより、Serializableインターフェイスを使用してカスタムシリアル化を実現できます。 JVMは特定のフィールドをシリアル化しませんが、デフォルト値でフィールドをファイルストレージに追加します。 そのため、カスタムシリアル化の場合はExternalizableを使用することをお勧めします。

4. 結論

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

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