それでは、“データ型”として利用できるオブジェクトとは、どのようなものでしょうか。もちろんこれは「値」として扱うことができるということです。「値を表現するためのオブジェクト」は、値を扱う場合と同じ使い勝手を提供することが必要です。このような性質を持つオブジェクトを本連載では「バリューオブジェクト」と呼ぶことにします。バリューオブジェクトであるためには、以下の要件を満たすことが必要となります。
大事なところなので、ちょっと細かく見ていきます。
Javaにおいて、オブジェクトを格納する変数は、正確にはオブジェクトへの参照を格納しています。「変数への代入に特別な配慮がいらないこと」とは、オブジェクトへの参照をそのまま代入しても、矛盾が起こらないということです。例を使って説明しましょう。リスト1のSpaceShipは、2次元座標上での場所のデータをインスタンス変数x、yに格納しています。SpaceShipの移動はmoveメソッドを使って行います。moveメソッドは、引数で渡された値をそのままインスタンス変数に代入しています。また、SpaceShipの現在位置はprintメソッドでコンソールに表示することができます。
public class SpaceShip { private int x; private int y; public void move(int x, int y) { this.x = x; this.y = y; } public void print() { System.out.println("x = " + x + ", y = " + y); } }
テストのためのプログラムClientで、SpaceShipの動作を確認します(リスト2)。Clientはmoveメソッドを用いてSpaceShipを移動させた後、SpaceShipの場所をコンソールに表示させています。プログラムで少し変わっているところは、SpaceShipの移動後、SpaceShipの移動に用いた変数xと変数yの内容を更新している点です。
public class Client { public static void main(String[] args) { SpaceShip ship = new SpaceShip(); int x = 100; int y = 200; ship.move(x, y); x = 200; y = 300; ship.print(); } }
Clientの動作は以下のようになります(実行結果1)。
$ java Client x = 100, y = 200
当たり前ですが、SpaceShipのmoveメソッドを呼んだ後で、変数の値を変えてもSpaceShipには影響を与えません。
次はSpaceShipの位置をプリミティブ型ではなく、2次元座標上の位置を表現するオブジェクトPoint(リスト3)を使って実現します。
public class Point { public int x; public int y; }
Point版のSpaceShipは、リスト4となります。moveメソッドは、引数で渡された値をそのままインスタンス変数に代入しています。
public class SpaceShip { private Point position; public void move(Point position) { this.position = position; } public void print() { System.out.println("x = " + position.x + ", y = " + position.y); } }
テストのためのプログラムClientはリスト5となります。moveメソッドの呼び出し前に、オブジェクトに値を設定。moveメソッドの呼び出しの後に、オブジェクトの値を更新しています。
public class Client { public static void main(String[] args) { SpaceShip ship = new SpaceShip(); Point point = new Point(); point.x = 100; point.y = 200; ship.move(point); point.x = 200; point.y = 300; ship.print(); } }
Clientの動作は以下のようになります(実行結果2)。
$ java Client x = 200, y = 300
今度は、このように値が変わってしまいました。本来は“x = 100、y = 200”となるのが想定された動作ですが、残念ながら“x = 200、y = 300”という結果となってしまいました。このように、データをプリミティブ型の変数を用いて処理しているときには発生しなかった問題が、オブジェクトを使うと発生してきます。
もう1つの問題は、データの比較です。まずプリミティブ型のデータの場合を見てみましょう。今度のSpaceShipはリスト6となります。isHitメソッドによって、現在の位置が、指定された座標にヒットしているかを調べることができます。
public class SpaceShip { private int x; private int y; public void move(int x, int y) { this.x = x; this.y = y; } public boolean isHit(int x, int y) { return (this.x == x && this.y == y); } }
この場合の、isHitメソッドの実装は値をそのまま比較すればよいだけです。動作確認のためのプログラムはリスト7となります。
public class Client { public static void main(String[] args) { SpaceShip ship = new SpaceShip(); int x1 = 100; int y1 = 200; ship.move(x1, y1); int x2 = 100; int y2 = 200; System.out.println(ship.isHit(x2, y2)); } }
それでは、実行してみましょう(実行結果3)。
$ java Client true
当然ですが、無事動作しました。
次は、オブジェクトを使った場合です。オブジェクトPointは先ほど使ったものと同じです(リスト8)。
public class Point { public int x; public int y; }
SpaceShipは、位置データの格納にオブジェクトPointを使うように変更されています(リスト9)。
public class SpaceShip { private Point position; public void move(Point position) { this.position = position; } public boolean isHit(Point position) { return (this.position.equals(position)); } }
動作確認のためのプログラムはリスト10となります。 変数point1と変数point2は、いずれもオブジェクトPoint型の変数です。まず変数point1に値を設定し、これを用いてSpaceShipのmoveメソッドを呼び出しています。次に変数point1に、変数point2に設定したものと同じ値を設定し、これを用いてSpaceShipのisHitメソッドを呼び出します。
public class Client { public static void main(String[] args) { SpaceShip ship = new SpaceShip(); Point point1 = new Point(); point1.x = 100; point1.y = 200; ship.move(point1); Point point2 = new Point(); point2.x = 100; point2.y = 200; System.out.println(ship.isHit(point2)); } }
それでは実行してみましょう(実行結果4)。
$ java Client false
今度の結果は期待どおりにはなりませんでした。本来はtrueになってほしいところですが、falseが表示されました。同じ内容を持つデータを比較したのにもかかわらず、比較の実行結果は“同じではない”となったわけです。
以上、今回はJavaにおけるデータ型の扱いについて説明しました。UMLのデータ型をそのままJavaの世界に持ってくるとクラス図が繁雑になってしまうという問題があります。この問題を解決するために“データ型”という概念を導入しました。
次回は、“データ型”を軸にJavaによるデータ型の実装の戦略、実装例を解説していきます。
Copyright © ITmedia, Inc. All Rights Reserved.