コンストラクタ
クラスを宣言すれば、次々にインスタンスを生成できることが分かりました。 しかし、複数のフィールドの初期値を一つずつ確実に設定していくのはなかなか大変なことです。 ここでは、インスタンスの初期化を簡潔かつ確実にやってくれるコンストラクタ(constructor)について学習しましょう。
コンストラクタ宣言
コンストラクタを宣言しておくと、インスタンスを生成した直後に様々な初期化の処理をさせることができます。 コンストラクタは、次のような形で宣言されます。
構文 : コンストラクタ宣言
クラス名(引数リスト){ 文1 文2 ... }
コンストラクタは、メソッドに似ていますが別物です。 コンストラクタの名前は、クラス名と同じでなければいけません。 また、コンストラクタは戻り値を返さないので、戻り値の型は指定しません。 それ以外はメソッドによく似ています。
英語のconstructorは「構築者」という意味です。
Robotクラスのコンストラクタは次のように宣言することができます。
class Robot { String name; //名前 int battery; //バッテリー残量 int x; //x座標 /*---- コンストラクタ ----*/ Robot(String n, int btry){ name = n; battery = btry; x = 0; } ... }
このコンストラクタでは、名前とバッテリー残量を表す仮引数n, btryの値を、それぞれ対応するフィールドに代入しています。 一方、フィールドxだけは仮引数と関係無く0を代入しています。
フィールドxの値は放っておいても既定値の0になるのですが、ここでは明示的に0を代入しています。 こうすれは、すべてのフィールドの初期値が一目瞭然になります。
- コンストラクタの名前は、クラス名と同じにする。
- コンストラクタには戻り値の型は指定しない。
コンストラクタを呼び出す
コンストラクタを利用するには、次のようにnew 演算子を使った式を記述します。 この式が評価されるときにインスタンスが生成され、その直後にコンストラクタが呼び出され、初期化の処理が行われるのです。
構文 : コンストラクタの呼び出し
new クラス名(引数リスト)
例えば、Robotクラスのインスタンスを生成して、名前に"ロボ太郎"、バッテリー残量に100を設定するには次のように記述します。
Robot robot1 = new Robot("ロボ太郎", 100);
ここでは、実引数として"ロボ太郎"と100を渡して呼び出しています。 1番目の仮引数は1番目の実引数で、2番目の仮引数は2番目の実引数で初期化されます。 従って、nは"ロボ太郎"で、btryは100で初期化されることになります。 そして、コンストラクタにより仮引数n, btryの値がそれぞれインスタンス変数name, batteryに代入されます。 その後xに0が代入され、インスタンスの初期化完了となります。
robot1には生成されたインスタンスへの参照が代入されます。
尚、コンストラクタはメンバではありませんので、次のようにクラス型変数robot1とメンバアクセス演算子.を使ってメソッドのように呼び出すことはできません。
robot1.Robot("ロボ太郎", 100);
new演算子によってインスタンスを生成した直後にコンストラクタが呼び出され、インスタンスの初期化処理が実行される。
Robotクラスのコンストラクタを使ってみる
では、実際にコンストラクタを宣言して利用する例を見てみましょう。 RobotTest04は、コンストラクタによってインスタンスを初期化した後、名前、x座標、バッテリー残量を表示するプログラムです。
RobotTest04.java
//ロボットクラス【第4版】 class Robot { String name; //名前 int battery; //バッテリー残量 int x; //x座標 /*---- コンストラクタ ----*/ Robot(String n, int btry){ name = n; battery = btry; x = 0; } /*---- x軸方向に歩く ----*/ boolean walkX(int dx){ int d = dx >= 0 ? dx : -dx; //dxの絶対値をdに代入しておく。 if(d <= battery){ x += dx; battery -= d; return true; }else{ return false; } } /*---- 状態を表示する ----*/ void showStatus(){ System.out.println("現在のx座標: " + x); System.out.println("バッテリー残量: " + battery); } /*---- 名前と状態を表示する ----*/ void show(){ System.out.println("名前: " + name); showStatus(); } } //ロボットクラス【第4版】のテスト用クラス class RobotTest04 { public static void main(String[] args) { //Robot robot1 = new Robot(); //デフォルトコンストラクタは作成されない。 Robot robot1 = new Robot("ロボ太郎", 100); Robot robot2 = new Robot("ロボ子", 90); robot1.show(); System.out.println(); robot2.show(); } }
RobotTest04の実行結果
名前: ロボ太郎 現在のx座標: 0 バッテリー残量: 100 名前: ロボ子 現在のx座標: 0 バッテリー残量: 90
RobotTest01からRobotTest03では、Robotクラスのインスタンスを次のように生成・初期化していました。
Robot robot1 = new Robot(); robot1.name = "ロボ太郎"; robot1.battery = 100;
ところがRobotTest04では、コンストラクタを宣言したことにより、次のように簡潔にインスタンスの生成・初期化ができてしまいました。 この違いは大きいですね。
Robot robot1 = new Robot("ロボ太郎", 100);
コンストラクタを利用することで、インスタンスを簡潔かつ確実に初期化することができる。
デフォルトコンストラクタ
ところで、RobotTest01からRobotTest03では、次のような記述でインスタンスを生成することができていました。
Robot robot1 = new Robot();
これは引数無しのコンストラクタを呼び出しているように見えますね。 でも、そのときはコンストラクタを宣言していなかったはずです。 どういうことでしょうか。
実は、ソースコードの中でコンストラクタが宣言されていない場合、コンパイラは仮引数が無く本体が空のデフォルトコンストラクタ(default constructor)を自動的に作成することになっているのです。 すなわち次のようなコンストラクタです。
Robot(){ }
デフォルトコンストラクタは、実際にはソースコードの中ではなく実行ファイルの中に作られます。
RobotTest01からRobotTest03では、デフォルトコンストラクタを呼び出していたわけですね。
ところが、RobotTest04ではデフォルトコンストラクタを呼び出そうとすると、コンパイルのときにエラーになります。 自前のコンストラクタを宣言した場合は、デフォルトコンストラクタは作成されなくなるためです。
RobotTest04.java:42: シンボルを見つけられません。 シンボル: コンストラクタ Robot() 場所 : Robot の クラス Robot robot1 = new Robot(); ^ エラー 1
コンストラクタを宣言しない場合、コンパイラによってデフォルトコンストラクタが自動的に作成される。
コンストラクタのオーバーロード
RobotTest04では、デフォルトコンストラクタが作成されなくなってしまいました。 それでは、取りあえずインスタンスを作っておいて、インスタンス変数の値は後から設定したい場合はどうすればいいのでしょうか。 簡単なことです。 引数無しのコンストラクタを自前で用意すれば良いのです。
メソッドと同様に、コンストラクタもオーバーロードすることができます。 即ち、仮引数の個数と型が異なる複数のコンストラクタを宣言することができるのです。 Robotクラスに2つのコンストラクタを追加してみましょう。 一つは仮引数が無く本体が空のコンストラクタ、もう一つは仮引数として名前だけを受け取るコンストラクタです。
RobotTest05は、複数のインスタンスをそれぞれ異なるコンストラクタによって初期化して、その結果を表示するプログラムです。
RobotTest05.java
//ロボットクラス【第5版】 class Robot { String name; //名前 int battery; //バッテリー残量 int x; //x座標 /*---- コンストラクタ ----*/ Robot(){ } Robot(String n){ name = n; battery = 0; x = 0; } Robot(String n, int btry){ name = n; battery = btry; x = 0; } /*---- x軸方向に歩く ----*/ boolean walkX(int dx){ int d = dx >= 0 ? dx : -dx; //dxの絶対値をdに代入しておく。 if(d <= battery){ x += dx; battery -= d; return true; }else{ return false; } } /*---- 状態を表示する ----*/ void showStatus(){ System.out.println("現在のx座標: " + x); System.out.println("バッテリー残量: " + battery); } /*---- 名前と状態を表示する ----*/ void show(){ System.out.println("名前: " + name); showStatus(); } } //ロボットクラス【第5版】のテスト用クラス class RobotTest05 { public static void main(String[] args) { Robot robot1 = new Robot("ロボ太郎", 100); Robot robot2 = new Robot("ロボ子"); Robot robot3 = new Robot(); robot1.show(); System.out.println(); robot2.show(); System.out.println(); robot3.show(); } }
RobotTest05の実行結果
名前: ロボ太郎 現在のx座標: 0 バッテリー残量: 100 名前: ロボ子 現在のx座標: 0 バッテリー残量: 0 名前: null 現在のx座標: 0 バッテリー残量: 0
コンストラクタをオーバーロードしたことで、次のようにいろいろな形でインスタンスを生成・初期化することが可能となりました。
Robot robot1 = new Robot("ロボ太郎", 100); Robot robot2 = new Robot("ロボ子"); Robot robot3 = new Robot();
尚、引数無しのコンストラクタではインスタンス変数に値を代入していません。 そのため、すべてのインスタンス変数が既定値によって初期化されています。 参照型の既定値はnull、int型の既定値は0でしたね。
コンストラクタをオーバーロードすることで、インスタンス初期化のための複数の方法を提供することができる。
フィールド、コンストラクタ、メソッドの並び順
クラスの中で、フィールド、コンストラクタ、メソッドを記述する順序は決まっていません。 次のように、ごちゃ混ぜに並んでいても、プログラムは問題なく動作します。
class Robot { String name; int battery; Robot(){ } Robot(String n){ ... } int x; boolean walkX(int dx){ ... } Robot(String n, int btry){ ... } void showStatus(){ ... } void show(){ ... } }
しかし、フィールド、コンストラクタ、メソッドの順に並べるのが一般的のようです。 特に理由がない限りこの順序で書く方が読みやすくなるでしょう。
フィールドの宣言がメソッドの宣言の間に記述されているようなケースもときどき見受けられますので、他人の作ったコードを読むときは注意しましょう。