前回、ファイルオブジェクトを生成し文字を書き込んだり読み込んだりする処理を書きました。
今回は、プログラマーが作ったクラスで生成したオブジェクトを入出力する方法について書きたいと思います。
##シリアライズ
生成したインスタンスをバイト列に変換したり(シリアライズ)、またそのバイト列をインスタンスに戻したり(デシリアライズ)する仕組みです。
この仕組みによって、入出力ストリームを使用してファイルに保存したりネットワークで伝送したりできます。
シリアライズは直列化とも呼ばれます。
##オブジェクトをシリアライズ可能にする
通常のクラスをシリアライズ可能にするには、java.io.Serializableインターフェースを実装する必要があります。このインターフェースはマーカーインターフェースのため、オーバーライドすべきメソッドは持ちません。
下のプログラムは、PersonクラスがSerializableインターフェースを実装し、シリアライズ可能なオブジェクトにしています。
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = "";
private int age = 0;
private transient String language = "Japanese";
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getInfo() {
return name + ":" + age;
}
public String getLang() {
return language;
}
}
##入出力するためにストリームを生成する
シリアライズ可能なオブジェクトの入出力は、ObjectOutputStreamクラスやObjectOutputStreamクラスを使用します。これらのストリームを使って、インスタンスを外部に保存したり、外部から復元したりできるようになります。
以下のプログラムは、前述のPersonクラスで生成したインスタンスを、ストリーム経由でファイルに保存、そこから復元をしています。
public class Main {
public static void main(String[] args) {
try (ObjectOutputStream outObject = new ObjectOutputStream(new FileOutputStream("object.txt"));
ObjectInputStream inObject = new ObjectInputStream(new FileInputStream("object.txt"));) {
outObject.writeObject(new Person("Taro",22));
Person exP = (Person)inObject.readObject();
System.out.println(exP.getInfo());
System.out.println(exP.getLang());
} catch (IOException | ClassNotFoundException e) {
}
}
}
「object.txt」というパスを持ったFileOutputStream、FileInputStreamを生成し、それらをもとにObjectOutputStream、ObjectInputStreamを生成します。
そして、writeObject()メソッドで指定したオブジェクト(直接"太郎、22歳"というPersonを生成)を、ObjectOutputStreamを経由して「object.txt」に書き込んでいます。
さらに、readObject()メソッドを使用して、「object.txt」に保存されたインスタンスを復元しPersonクラスの変数に格納しています。readObject()メソッドの戻り値はObject型であるため、exPに代入する前にPersonクラス型へキャストしないといけません。
※readObject()メソッドはClassNotFindExceptionをスローする可能性があるため、それに対応した例外処理を書く必要があります。
ちなみに、上記のプログラムを実行すると、以下の結果が出力されます。
Taro:22
null
2行目のnullが出力される理由としては、後述の「transient」キーワードを参照ください。
##シリアライズ対象外の変数
Serializableインターフェースを実装したクラスでも、以下の変数はシリアライズできません。
・transientで指定された変数
・static変数
・Serializableインターフェースを実装していないクラス型の変数
transientはデシリアライズ時にnullの状態で復元されます。先ほどのプログラムで、nullが返されたのもこれが理由です。
またstatic変数は、static領域に値を持つため、復元時はそこの値が使われます。
##シリアライズの継承
シリアライズ可能なクラスのサブクラスは「implements Serializable」を書かなくても暗黙的にシリアライズ可能です。
また、スーパークラスがSerializableインタフェースを実装していない状態で、サブクラスがSerializableインタフェースを実装している場合、デシリアライズの際にスーパークラスのコンストラクタが呼び出されてインスタンス化されます。
##シリアルバージョンID
Personクラスのフィールドに、「serialVersionUID」という定数を宣言しています。
この定数はシリアルバージョンIDというもので、復元前後でクラスが変更されていないか識別するために使います。(eclipseではこれを書かないと警告が出ます。)
クラス修正時にシリアルバージョンIDを修正することで、修正前に保存したインスタンスを修正後のクラスで復元しようとすると例外(InvalidClassException)が発生します。
これによって、矛盾した状態での復元がなされた時、それを知らせることができます。
<参考>
書籍
・スッキリわかるJava入門 実践編 第2版
・オラクル認定資格教科書 Javaプログラマ Gold SE 8
ホームページ
・マコトのおもちゃ箱 ~ぼへぼへ自営業者の技術メモ~
http://piyopiyocs.blog115.fc2.com/blog-entry-205.html