オブジェクト指向の考え方を用いることで、 プログラムの書き方に対する考え方が変化したはずです。 オブジェクト指向プログラミングでは、 プログラムで扱うべき対象をオブジェクトとして形式化し、 オブジェクトの属性と振る舞いを考えるということが重要になります。
今回は、引き続きクラス定義の説明を行います。 まず、クラス定義における変数のスコープ (有効範囲) や、 メソッドのオーバロード (多重定義) について説明し、 次に、コンストラクタと呼ばれる オブジェクトを生成する際にオブジェクトの初期化を行う方法について説明します。
オブジェクト指向の考え方では、 オブジェクトに対して仕事をさせるためにメッセージを用います。 メッセージは、オブジェクトに対して処理を依頼するという意味があります。 Javaではメッセージはメソッドの実行として実現されています。
現実世界での犬と猫というオブジェクトについて考えてみます。 これらのオブジェクトに「鳴け」というメッセージを送った場合、 犬であれば「ワン」と鳴き、猫であれば「ニャン」と鳴きます。
また、コンピュータの世界では、 ファイルというオブジェクトに対して「開け」というメッセージを送った場合、 それぞれのファイルの種類によって、 それに適した異なるアプリケーションが起動します。
つまり、オブジェクトに対して「鳴け」や「開け」というメッセージを送れば、 細かいことはオブジェクトが自動的に判断して、 その場の状況に合わせて適当な方法で目的の仕事をやってくれるということです。 メッセージの送り主は、 オブジェクトが所期の仕事を行ってくれることを信頼して、 あとはオブジェクトに任せてしまえば良いのです。
このように、同じメッセージであっても、 そのメッセージの送り先やパラメータの内容により、 その場に応じて異なる挙動をする、という考え方があります。 これを、ポリモフィズム (Polymorphism) や多相性、 あるいは多態性と言います。
エッセンシャルJava pp.145-147 を参照してください。
Javaでポリモフィズムを実現する機能の一つが、 メソッドのオーバロードです。 これは、同じメソッド名でも、 返り値の型または引数の宣言のいずれか異なっていれば、 別のメソッドとして宣言できるというものです。
次のプログラムは円を表すクラス定義部分です。
class Circle { int x, y; int radius; double PI = 3.14; void setCoordinates() { x = 0; y = 0; radius = 1; } void setCoordinates(int a, int b) { x = a; y = b; radius = 1; } void setCoordinates(int a, int b, int c) { x = a; y = b; radius = c; } }
このクラスにはメソッド setCoordinates の宣言が 3 回行われています。 このメソッドは返り値が void 型すなわち値を返さないメソッドですが、 引数の数によって異なった動作をします。
最初のメソッドは、 引数をすべて省略して setCoordinates を実行したときに、 実際に実行されるメソッドの内容を宣言しています。 メソッド内では、 座標 (0, 0) の半径 1 の単位円、という属性を登録しています。
2番目のメソッドは、 int 型整数 2 つが引数に与えられたときに 実行されるメソッドの内容を宣言しています。 引数で与えられた値の座標にある半径 1 の単位円、という属性を登録しています。
3番目のメソッドは、 int 型整数 3 つが引数に与えられたときに 実行されるメソッドの内容を宣言しています。 1 番目と 2 番目の引数で与えられた値の座標にある、 3 番目の引数で与えられた値の半径の円、という属性を登録しています。
このように、 同じ setCoordinates という名前のメソッドでも 引数の種類や数によって、 異なった仕事をするようにできます。 同じメソッド名に対して複数の実装を行うことを、 メソッドのオーバロード (Overload) と言います。 オーバロードを行ったメソッドは、 それぞれが独立した一つのメソッドのように動作します。
この例では以下の 3 つのメソッドの宣言がありました。
void setCoordinates() void setCoordinates(int a, int b) void setCoordinates(int a, int b, int c)
結果の型、引数の型、引数の数の組み合せをメソッドのシグネチャ (Signature) と呼びます。 これらの 1 つでも異なれば、シグネチャも異なったものとして扱われます。 オーバロードは、 同じメソッド名でシグネイチャが異なるメソッドについて行うことができます。
次の例ではオーバロードが可能なメソッド宣言の先頭部分を示します。
int myMethod() void myMethod() void myMethod(int a) double myMethod(int a, double x) double myMethod(double x, int a)
特に最後の 2 つは、 いずれも int 型と double 型の引数を 1 つずつ取るメソッドですが、 引数の宣言順が異なるため、異なったシグネチャとなりオーバロードが可能です。
次の例はオーバロードの間違った例です。
double myMethod(int a, int b) double myMethod(int b, int a) double myMethod(int x, int y)
シグネチャは戻り値や引数の型で決定されます。 上の例では変数名が異なっていますが、いずれも int 型であるため、 オーバロードできません。 ここでの変数 (仮引数) はメソッドの実行元での変数 (実引数) とは独立したものであるので、 メソッドの実行元では int 型の引数 2 つ、 という情報だけしか有効ではないからです。
エッセンシャルJava pp.148-151 を参照してください。
インスタンスを生成しただけでは、 その属性には何も値が入っていない状態です。 場合によっては、 オブジェクトとしてプログラムの中で使えるようにするために、 まず最初に属性の値を登録することが必要になります。 インスタンスを生成する際に、 あらかじめ値を入れておくことが必須の属性を初期化しておく等のことを、 同時に行いたいことが多くあります。 コンストラクタ (Constructor) は、 クラスからインスタンスを生成するときに初期化を行うものです。
コンストラクタを用いたプログラムの例を 先週の TDUStudentMain.java を使い説明します。
下のプログラム片はmainの一部です。
public static void main(String[] args) { TDUStudent alice; alice = new TDUStudent(); alice.setIdentifier(1, "Alice");
メソッド setIdentifer によって、 学籍番号と名前を alice に登録しています。 このような処理は、 インスタンスを生成する際に行う初期化処理で行うのが適しています。 そこで、次のように書き換えることを考えてみます。
TDUStudent alice; alice = new TDUStudent(1, "Alice");
new 演算子で、 クラス TDUStudent のインスタンス生成する際に、 学籍番号と名前を示す 2 つの引数を与えています。 クラス TDUStudent ではインスタンスを生成する際に これらの引数によってインスタンスの初期化が行われるようにします。
一方、クラス TDUStudent の定義部分には インスタンスの生成時に 1, "Alice" という引数が与えられたとき、 インスタンスをどのように初期化すべきかを書いておきます。 このように、インスタンスの生成時に行う 初期化の手順をコンストラクタと呼びます。
以下のプログラム片は TDUStudent のクラス定義です。 コンストラクタを追加すると次のようになります。
class TDUStudent { int id; String name; int math; int english; int physics; TDUStudent(int i, String n) { id = i; name = n; } ......
コンストラクタでは int 型整数 i と文字列 n を受け取り、 属性 id と name にそれぞれ代入しています。 このように、 インスタンスの生成と同時に属性を正しい値に設定するために、 コンストラクタは用いられることが多いのです。
コンストラクタはメソッドの一種のような書き方をします。 クラス名と同名のメソッドとして、結果の型を書かずに宣言します。 コンストラクタの中身にインスタンスの初期化処理を書きます。 return 文で値を返すことはできません。 コンストラクタの宣言を含んだクラス定義の文法は次のようになります。
class クラス名 { 属性の宣言 ...... クラスと同じ名前 ( 引数, ... ) { コンストラクタで行う処理の中身 } メソッドの宣言 ...... }
Javaでは、 コンストラクタを定義していないプログラムの場合、 自動的にコンストラクタが作られます。 これを、暗示的コンストラクタ (Implicit Constructor) と呼ぶことがあります。
暗示的コンストラクタは引数の無いコンストラクタで、 主な働きは属性の値を初期化することです。 整数や実数の属性はすべて 0 にし、 文字列、配列やインスタンスの属性は空 (null, ナル) の状態にします。 空の状態とは、 変数が対象となるデータを指し示していない状態と考えてください。
先週の TDUStudentMain.java では コンストラクタを宣言していません。
TDUStudent alice; alice = new TDUStudent();
上のように new 演算子でクラス TDUStudent のインスタンスが作られる際に、 引数の無しコンストラクタを実行しています。 ここで、暗示的コンストラクタが実行されているのです。
クラス定義の中に明示的にコンストラクタの宣言が行われていると、 暗示的コンストラクタは作られなくなります。
前章ではメソッドのオーバロードについて説明しました。 コンストラクタもオーバロードすることが可能です。
先ほどの例題、クラス Circle をコンストラクタを用いて書き換えてみます。
class Circle { int x, y; int radius; double PI = 3.14; Circle() { x = 0; y = 0; radius = 1; } Circle(int a, int b) { x = a; y = b; radius = 1; } Circle(int a, int b, int c) { x = a; y = b; radius = c; } }
オーバロードしたコンストラクタの中で、 別のシグネチャを持つコンストラクタを実行するようにした方が良い場合があります。
オーバロードされた 3 つのメソッド setCoordinates はどれも 中身の処理では、 X座標、Y座標、半径という属性に値を代入しています。 もし、仮にこのクラスを、 座標-半径の形式から、 媒介変数による方程式の形式で表現するように改造する必要があるとすると、 3 つのコンストラクタ全てを改造する必要が生じてしまいます。
そこで、X座標、Y座標、半径を登録する働きをする 3 つ目のコンストラクタを、 他のコンストラクタからも利用するようなプログラムを考えてみます。 このような場合、 this キーワードを用います。
class Circle { int x, y; int radius; double PI = 3.14; Circle() { this(0, 0, 1); } Circle(int a, int b) { this(a, b, 1); } Circle(int a, int b, int c) { x = a; y = b; radius = c; } }
this キーワードは、 クラス定義の中で自分自身を表すのに用います。 上の例のように this( 引数, ... ) と書くと、 自分自身のコンストラクタを実行せよ、という意味になります。
下の例では、 コンストラクタに引数を与えずにインスタンスを生成しています。 引数のないコンストラクタをデフォルトコンストラクタと呼びます。
Circle cir = new Circle();
繰り返しになりますが、 明示的にコンストラクタの宣言を行っていないクラスについては、 デフォルトコンストラクタが 暗示的コンストラクタとして自動作成されます。 しかし、コンストラクタが一つでも宣言されていると、 暗示的コンストラクタは作成されません。
クラスを定義するときには、 自分の定義したクラスが他の場所で (あるいは、他の人によって) 再利用されるということを意識すると良いでしょう。 コンストラクタについても、 引数なしでインスタンスが生成できるように、 デフォルトコンストラクタを宣言するのが好ましい場合が多いのです。 なぜそのようにするのかについて詳しくは後の回で説明します。