前回はUMLのデータ型とJavaのマッピングについて概観しました。その結果Javaへのマッピングのために、値を表現するオブジェクトであるバリューオブジェクトを導入する必要があることが分かりました。今回は、このバリューオブジェクトをJavaで実装する方法について考えてみましょう。このJavaオブジェクトは以下の要件を満たす必要があります。
「変数への代入に特別な配慮がいらないこと」を満たすためには、変数に代入した後に、変数の所有者の意図しないところで、オブジェクトの内容が変更されないことを保証する必要があります。
このためには、いろいろな方法が考えられますが、最も汎用的な方法はイミュータブルオブジェクト(immutable object)を利用する方法です。イミュータブルオブジェクトは、「不変オブジェクト」と呼ばれることもありますが、文字どおり内容を変更することができないオブジェクトです。
内容を変更することができないオブジェクトは、変数の所有者の意図しないところでオブジェクトの内容が変更されることはあり得ませんから、「変数への代入に特別な配慮がいらないこと」という要件を満たすことができるわけです。
equalsメソッドのデフォルトの動作は、オブジェクトが同じなのか、つまりオブジェクトのIDが同じなのか、という点の比較を行うことです。
オブジェクトにおける同定は、「同じ個体であるのか」、つまり「比較対象のオブジェクトが同じIDを持っているのか」ということでありメソッドのデフォルトの動作はこの方針にのっとったものであるわけです。
「内容による比較ができること」を満たすには、equalsメソッドの動作を、 “内容による比較”に変更しなければならないということです。
値を表現するオブジェクトは、値の操作を行うための機能を実装することで、使いやすいオブジェクトになります。可能であれば以下の機能も実装するとよいでしょう。
値であるということは意味のある内容を文字列で表現できる可能性が高くなります。このメリットを生かすためにtoStringメソッドを実装するとよいでしょう。適切な内容を表示するtoStringメソッドが実装されていれば、少なくともデバッグ時には役に立ちます。
toStringメソッドで適切な内容を表示できるようになっているのであれば、この内容の文字列表現からオブジェクトを生成できると大変便利です。このため、文字列を引数にしたコンストラクタを作成することをお勧めします。
Stringを引数にしたコンストラクタのバリエーションとして、コンストラクタだけでなく、クラスメソッドによるオブジェクトの生成、ファクトリクラスによるオブジェクトの生成も考えられます。
java.lang.Cloneableをimplementsすると、cloneメソッドでオブジェクトの複製を作れるようになります。
イミュータブルオブジェクトの場合は、java.lang.Cloneableである必然性はありませんが、データである以上java.lang.Cloneableであることは可能なはずです。このため、余裕があればjava.lang.Cloneableにしておいた方が得といえます。
java.io.Serializableをimplementsすると、オブジェクトを直列化して、外部との入出力を行えるようになります。具体的にはRMIで引数にできるという大きなメリットが出てきます。
データである以上java.io.Serializableであることは可能なはずです。また、java.io.Serializableにすることの手間はほとんどないので、できるだけjava.io.Serializableにしておいた方が得です。
大小比較が可能である場合には、java.lang.Comparableをサポートしておいた方が得です。コレクションライブラリを使って、ソーティングなどのサービスを受けられるようになります。
Javaにおけるバリューオブジェクトの実例として、基本クラスであるjava.lang.Stringとjava.lang.Integerをチェックしてみます。もちろん、バリューオブジェクトとして必須の要件である「イミュータブルオブジェクト」と「内容を比較するequalsメソッド」であるのかという点がポイントとなります。
java.lang.Stringは、いうまでもありませんが、完全なイミュータブルオブジェクトとして実現されています。java.lang.Stringの内容はコンストラクタでしか設定することができません。文字列の操作が必要な場合には、java.lang.StringBufferを用いるようになっています。
java.lang.Stringの、equalsメソッドは、文字列の内容を比較するようになっています。これも、バリューオブジェクトの定義どおりです。
以上の点から、java.lang.Stringはバリューオブジェクトの要件を満たしていることが分かります。
そのほか、バリューオブジェクトとして実装していると便利な機能として、以下のものが実装されています。
java.lang.Integerも、java.lang.Stringと同様に完全なイミュータブルオブジェクトとして実現されています。java.lang.Integerの内容はコンストラクタでしか設定することができません。java.lang.Integerの内容を操作する場合には、int型のデータとして取り出したものを操作したうえで、新しいjava.lang.Integerを生成することになります。
java.lang.Integerの、equalsメソッドは、文字列の内容を比較するようになっています。これも、バリューオブジェクトの定義どおりです。
以上の点から、java.lang.Integerはバリューオブジェクトの要件を満たしていることが分かります。
そのほか、バリューオブジェクトとして実装していると便利な機能として、以下のものが実装されています。
以上のように、java.lang.String、java.lang.Integerのいずれもバリューオブジェクトとしての要件を満たしていることが分かりました。このため
“データ型”として扱っても問題がないわけです。
もちろん、Javaのデータ型(第5回 表2)に示したそのほかの “データ型”はいずれもバリューオブジェクトとしての要件を満たしています。地味なテーマなのであまり大きく取り上げられることはありませんが、Javaがデータ型に対してきちんとした対応を行っているオブジェクト指向言語であることが分かります。
Copyright © ITmedia, Inc. All Rights Reserved.