デベロッパーズコーナー:Javaプログラミングを極める「XMLEncoder/XMLDecoder」
2003年11月17日作成
Javaプログラミングを極める
第7回:XMLEncoder/XMLDecoder
(株)日本ユニテック
太田 純
<この記事はDigital Xpress 2002 Vol.13(2-3月号)に掲載されたものです>
J2SE1.4では、新しいシリアライズメカニズムとして2つのクラス、XMLEncoderおよびXMLDecoderが導入されました。これらのクラスはJavaBeansコンポーネントの長期保存を目的として開発されましたが、他のさまざまな用途で用いることができます。 |
J2SE 1.4 のリリースから1 年が経とうとしています。JAXP(Java API for XML
Processing)や、JCE(Java Cryptography Extension)など、有用な機能がJ2SE
の一部として組み込まれたため、とりわけネットワーク対応アプリケーションをクライアントに容易に配布するのに役立ってきたことでしょう。
J2SE 1.4 の新機能一覧*1を見ますと、「JavaBeans
コンポーネントの長期持続性」という項目があります。およそXML と関係ありそうには見えないこの機能は、XML
を使って実現されており、いくつかの要件を満たしさえすれば、いわゆるBean として作成されたクラスでなくてもこの機能の恩恵にあずかることができます。*2
従来のシリアライズ機能
多くのアプリケーションにおいて必要となる重要な機能は、アプリケーションが扱うデータや設定情報などを、何かの形で
保存したり転送したりすることです。従来から、Java 言語に組み込まれた機能として「シリアライズ(serialization)」とい
うしくみが提供されていました。この機能はオブジェクトをバイトストリームに変換し、バイトストリームからオブジェクト
に読み戻すことを可能にしました。ストリームをファイルと関連付けておけば、ファイルの読み書きを行うことができ、スト
リームを特定の転送プロトコルと関連付けておけばデータ転送を行うことができたわけです。
これを実現するために、シリアライズ可能なオブジェクトをマークするためのjava.io.Serializable インタフェースと、実際
のシリアライズ/ディシリアライズを行うクラスjava.io.ObjectOutputStream とjava.io.ObjectInputStream が提供されてきま
した。これによって、オブジェクトの高速なストリーミングを行うことができたのです。
これらのシリアライズ機能は、ストリーム― オブジェクト間の高速な読み書きを実現しますが、重要な問題点がありました。それは、オブジェクトのバージョンの変化に対応するのが困難だという点です。ストリーミング対象となるクラスにフィールドが追加された場合、対応するバイナリ形式が変更され、古い形式のオブジェクトの読み込みは不可能となるか、または困難になります。クラス内のいかなる変更も、バイナリ形式では互換性のないバージョンを作り出すことになるからです。
さらに、複数のオブジェクト間で複雑な参照関係がある場合に、それをどのように書き出すべきかが常に問題となります。この場合、開発者がストリーミングの方法や順序を指定するためにプログラミングする必要があります。
このことは特にJavaBeans コンポーネントの開発において問題となってきました。Bean のバージョンが更新されたときに、過去のバージョンのオブジェクトが読み込めなかったり、Bean の開発に使ったのとは異なる開発ツールで(それが同じツールの新しいバージョンであったとしても)読み込めなかったりする事態が生じたからです。
XMLEncoder / XMLDecoder
従来のシリアライズ機能が抱える問題点を解決するために、JCP(Java Community Process)において「JSR
57 Long-Term Persistence for JavaBeans Speci.cation」が検討され、その結果がJ2SE
1.4 に搭載されました。この機能の開発において目標とされたのは以下の点です。
● 従来のシリアライズ機能を利用するアプリケーションに影響を与えないため、シリアライズ機能の更新をするのではなく、新しい機能として作成する
● クラス内のどの項目をシリアライズ対象とするのか、開発者が完全に把握することを可能とする
● 保存形式は開発ツールに依存せず、バージョンの変化に対して追従可能とすること
そして、これらの目的をサポートするのに最も適切な方式として採用されたのがXML 形式による入出力です。
歴史的な説明はこのくらいにして、実際のコードを見てみましょう。オブジェクトobj をファイル"test.xml"
に書き出す場合、次のようなコードになります。XMLEncoder およびXMLDecoder クラスは、java.io
パッケージではなく、java.beans パッケージに含まれていることに注意してください。
XMLEncoder e = new XMLEncoder(
new BufferedOutputStream(
new
FileOutputStream("test.xml")));
e.writeObject(obj);
e.close(); |
XMLEncoder によって保存したファイルの読み込み処理を行うには、次のようにします。
XMLDecoder d = new XMLDecoder(
new BufferedInputStream(
new
FileInputStream("test.xml")));
Object obj = d.readObject();
d.close(); |
XMLDecoder のreadObject メソッドの戻り値はObject 型なので、適切な型にキャストして使用することになります。
XMLEncoder で保存可能なクラスを作る
XMLEncoder で保存し、XMLDecoder で再読み込み可能なクラスを作るためのもっとも簡単な方法は、JavaBeans
仕様に従うクラスを作成することです。とはいっても、Swing コンポーネントで使用するような視覚的要素のためのBeansでなければ、それほど多くのルールがあるわけではありません。保存対象にするプロパティについて、JavaBeans
と同様の命名規則に従うアクセスメソッドを作ればよいというだけのことです。つまり、次のルールです。
設定メソッド:set + プロパティ名
取得メソッド:get + プロパティ名
boolean 型のプロパティの取得メソッドは次の形式も可
is
+ プロパティ名 |
たとえば、文字列プロパティ「name」のための設定メソッドは
public void setName(String name) |
となり、取得メソッドは
となります。同様に、boolean型プロパティ「running」のための設定メソッドは
public void setRunning(boolean running) |
であり、取得メソッドは
public boolean isRunning() |
となるわけです。
もう1つ必要なことは、デフォルトコンストラクタ、つまりpublic で引数なしのコンストラクタを作っておくことです。XMLDecoder
のデフォルトの動作は非常に簡単に作られており、要約すればデフォルトコンストラクタを呼んでオブジェクトを構築し、各設定メソッドを呼び出してプロパティをセットするだけのことです。ですから、XMLDecoder
がそのまま呼び出せるコンストラクタを用意しておくのは重要なことになるわけです。
クラスに対する制限などは特にありません。従来型のシリアライズ機能ではjava.io.Serializable
インタフェースを実装する必要がありましたが、XMLEncoder を使う場合はそのようなインタフェースは必要ありません。XMLEncoder
で利用可能なクラスの例をリスト1 とリスト2
に示します。
リスト1 に示したクラスProduct は、先に例として挙げた文字列とboolean
のプロパティのほかに、オブジェクトのプロパティも持っています。参照しているオブジェクトはリスト2に示したクラスPerson
です。ここでは簡単にするため、名前を保持するだけのクラスにしました。
では、実際にXMLEncoder を使って保存してみましょう。プログラム例をリスト3
に示しました。ここで、保存対象としたのは、Product オブジェクトの配列です。配列に設定したProduct オブジェクトのうち、arrayProducts[1] とarrayProducts[2]
は、同じPerson オブジェクトを参照しています。
このプログラムを実行した結果として生成されるXML ファイルをリスト4
に示しました。配列に対してarray要素、オブジェクトに対してobject 要素といった形で保存されていることがわかります。
ここで、同じPerson オブジェクトを参照していた部分を見ると、最初に参照したobject 要素にid
属性が付いており( リスト4-(A))、2 回目に参照したobject
要素では同じ属性値を持つidref 属性が付いていて、内容は省略されています(
リスト4-(B))。これにより、読み込み時に同じ内容のオブジェクトが2つ作られてしまうのではなく、1
つのオブジェクトを2 箇所から参照した元通りの形で復元することが可能になるわけです。
もう1 つ注目できるのは、すべてのプロパティが書き出されているわけではないということです。このサンプルの場合、3つのProduct
オブジェクトのうち、running プロパティが書き
出されているのは3 番目のものだけです( リスト4-(C))。ためしに、デフォルトコンストラクタで
public Product() { running = true; } |
というように、running プロパティの初期値をtrue に変えてから同じテストプログラムを実行すると、XMLEncoder
は3番目のProduct オブジェクトについてはrunning プロパティを
出力せず、1 番目と2 番目のほうを出力するようになります。デフォルトコンストラクタで復元できるプロパティは出力しないことにより、出力ファイルのサイズと処理時間を縮小できるようにしているのです。そして、このことは同時に、保存した後にクラスを修正してプロパティを追加したとしても、デフォルト値を使って読み込むことができることを意味します。
最後に、XMLDecoder を使ってオブジェクトを復元するプログラム例を示しておきます(リスト5
参照)。このプログラムは、XMLEncoder で保存したデータをXMLDecoderで読み込んだ後、結果をコンソールに表示するだけのものです。このプログラムを実行すると以下のような出力が得られ、正しく読み込まれたことを確認できます。
冬が来る前に: 経塚, false
神田川: 高橋, false
さとうきび畑: 高橋, true |
今回は、新しいシリアライズメカニズムである、XMLEncoder およびXMLDecoder を紹介しました。本稿では扱いませんでしたが、標準的な名前とは異なる設定/取得メソッドを使う方法や、引数付きコンストラクタを使う方法も備えられています。オブジェクトの長期保存やリモートマシン上のJava
プログラムへオブジェクトを転送する手段として利用してみてください。
【リスト1 : XMLEncoder で利用可能なクラス(Product.java)】
public class Product {
private String name;
private Person arranger;
private boolean running;
public Product() {}
public Product(String name, Person arranger, boolean
running)
{
this.name = name;
this.arranger = arranger;
this.running = running;
}
public void setName(String name) { this.name = name;
}
public String getName() { return name; }
public void setArranger(Person arranger) { this.arranger
= arranger; }
public Person getArranger() { return arranger; }
public void setRunning(boolean running) { this.running
= running; }
public boolean isRunning() { return running; }
} |
【リスト2 : XMLEncoder で利用可能なクラス(Person.java)】
public class Person {
private String name;
public Person() {}
public Person(String name) { this.name = name; }
public void setName(String name) { this.name = name;
}
public String getName() { return name; }
} |
【リスト3 : XMLEncoder テストプログラム(XMLEncoderTest.java)】
import java.beans.XMLEncoder;
import java.io.*;
public class XMLEncoderTest {
public static void main(String[] args) {
Person psnK = new Person(" 経塚");
Person psnT = new Person(" 高橋");
Product[] arrayProducts = new Product[3];
arrayProducts[0] = new Product(" 冬が来る前に",
psnK, false);
arrayProducts[1] = new Product(" 神田川",
psnT, false);
arrayProducts[2] = new Product(" さとうきび畑",
psnT, true);
try {
XMLEncoder enc = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("productlist.xml")));
enc.writeObject(arrayProducts);
enc.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
}
}
} |
【リスト4 : リスト3 のプログラムで出力されたXML ファイル】
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.1" class="java.beans.XMLDecoder">
<array class="Product"length="3">
<void index="0">
<object class="Product">
<void property="arranger">
<object class="Person">
<void property="name">
<string> 経塚</string>
</void>
</object>
</void>
<void property="name">
<string> 冬が来る前に</string>
</void>
</object>
</void>
<void index="1">
<object class="Product">
<void property="arranger">
<object id="Person0" class="Person"> (A)
<void property="name">
<string> 高橋</string>
</void>
</object>
</void>
<void property="name">
<string> 神田川</string>
</void>
</object>
</void>
<void index="2">
<object class="Product">
<void property="arranger">
<object idref="Person0"/> (B)
</void>
<void property="name">
<string> さとうきび畑</string>
</void>
<void property="running">
<boolean>true</boolean> (C)
</void>
</object>
</void>
</array>
</java> |
【リスト5 : XMLDecoder テストプログラム(XMLDecoderTest.java)】
import java.beans.XMLDecoder;
import java.io.*;
public class XMLDecoderTest {
public static void main(String[] args) {
Product[] arrayProducts;
try {
XMLDecoder dec = new XMLDecoder(
new BufferedInputStream(
new FileInputStream("productlist.xml")));
arrayProducts = (Product[])dec.readObject();
dec.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
return;
}
for(int i = 0; i < arrayProducts.length; i++)
{
System.out.println(arrayProducts[i].getName()
+ " : " + arrayProducts[i].getArranger().getName()
+ " , " + arrayProducts[i].isRunning());
}
}
}
|
※1
http://java.sun.com/j2se/1.4/ja/docs/ja/relnotes/
features.html#persistence
※2 本稿で扱うXMLEncoder/
XMLDecoder を利用する場合、J2SE のバージョンを1.4.1 以上にバージョンアップしておくことをお勧めします。バージョン1.4.0
のXMLEncoder にはバグが存在していたからです。
関連サービス
IT技術およびIT製品の可用性調査・検証業務