4.5 コンストラクタ
4.5.1 オブジェクトの初期化
多くのフィールドを保有するクラスを扱う場合、インスタンスを生成した後にオブジェクトのフィールドを目的の状態に初期化するにはかなりの手間がかかります。特に、こういった作業は、常に間違いを犯す可能性を持つ人間にやらせると必ずバグの温床となります。
そこで Java 言語では、こうしたオブジェクトの初期化忘れなどを防ぐために、インスタンスを生成する時、自動的に呼び出される初期化専用のメソッドがあります。これをコンストラクタと呼びます。クラスにコンストラクタを与えることによって、常にオブジェクトを正しく初期化できるようになります。コンストラクタ宣言は、基本的にメソッド宣言と同じですが、戻り値は持たず、名前をクラス名と同じにしなければなりません。
class クラス名 { クラス名(仮パラメータリスト) { 本体 } }
初期化に必要な情報は、仮パラメータから受け取ることができます。コンストラクタの本体はメソッドと同様に記述することができるため、初期化に必要な代入処理以外の制御を記述することもできます。例えば、ネットワーク制御を行うクラスであれば、初期化以外に、コンピュータがネットワークに接続されているかどうかなど、オブジェクトの役割に必要な情報を集めるなどの処理を記述しても良いでしょう。
コンストラクタは常にクラスに結び付けられ、インスタンスを保有しているので this キーワードを使うこともできます。コンストラクタの呼び出しは、クラス・インスタンス生成式で行われます。実は、クラス・インスタンス生成式で new の後に指定したメソッドのようなものは、このコンストラクタだったのです。コンストラクタに戻り値が存在しないのは、コンストラクタの戻り値は常に生成したオブジェクトだからです。
コンストラクタを指定しない場合、クラスは仮パラメータを持たず、初期化処理を一切行わないコンストラクタを持っています。必要であれば、初期化に必要な仮パラメータを持つコンストラクタを宣言し、クラス・インスタンス生成式で引数を渡すように指示することもできるのです。コンストラクタの起動は、メソッドの起動と同じ考え方でよいでしょう。
class Color { int r , g , b; Color(int r , int g , int b) { this.r = r; this.g = g; this.b = b; System.out.println("R = " + r + " : G = " + g + " : B = " + b); } } class Test { public static void main(String args[]) { Color color = new Color(0xFF , 0xAA , 0xAA); } }
>java Test R = 255 : G = 170 : B = 170
コード1は、Color クラスで Color() コンストラクタを明示的に宣言しています。このコンストラクタは初期化を行うための 3 つの整数を受け取ります。通常、コンストラクタに初期化以外の処理を書いてはいけませんが、このプログラムでは、コンストラクタが正しく起動し、値が渡されていることを確認するために println() メソッドで、コンストラクタに渡された引数を表示しています。実行結果を見れば、コンストラクタが正しく起動されていることがわかります。
もちろん、コンストラクタもメソッド同様にオーバーロードすることができます。コンストラクタをオーバーロードすることによって、クラス利用者にオブジェクトの初期化方法を選択させることができるでしょう。
4.5.2 明示的なコンストラクタ起動
コンストラクタをオーバーロードすることで、初期化方法が得られるようになり、便利になるでしょう。しかし、その多くは初期化に対してクラスの開発者があらかじめ定めた初期値で初期化するか、コンストラクタに値を渡し明示的に初期値を定めるかの違いであり、処理には一貫性があります。
例えば、文字列方のフィールド name を初期化するとき、引数を受け取らないデフォルトのコンストラクタの場合は "名称不明" で初期化し、文字列を受けるコンストラクタでは、パラメータでフィールドを初期化すると考えてください。デフォルトのコンストラクタに要求される処理は "名称不明" という文字列を、もう 1 つのコンストラクタに渡すことに等しいのです。だとすれば、それぞれが独自のコードを実装するよりも、コンストラクタから別のコンストラクタを呼び出すことで、コードを統一するべきだと考えられるでしょう。
実は、コンストラクタから別のコンストラクタの呼び出しが可能です。これを明示的なコンストラクタ起動と表現します。ただし、明示的なコンストラクタ起動はメソッド起動式のように自由にできるものではなく、必ずコンストラクタ内のブロックの先頭で呼び出さなければならないとされています。明示的なコンストラクタ起動は次のようになります。
this(引数リスト);
これで、自分自身のオーバーロードされている他のコンストラクタを呼び出すことができます。これを代替コンストラクタ起動と呼びます。ただし、自分自身のコンストラクタを呼び出すことはできません。再帰的なコンストラクタの呼び出しが確認された場合はコンパイル・エラーとなります。
class Kitty { String name; Kitty() { this("名称不明"); } Kitty(String name) { this(); this.name = name; } } class Test { public static void main(String args[]) { Kitty kitty1 = new Kitty(); Kitty kitty2 = new Kitty("さくら"); System.out.println("kitty1.name = " + kitty1.name); System.out.println("kitty2.name = " + kitty2.name); } }
>java Test kitty1.name = 名称不明 kitty2.name = さくら
コード2の Kitty クラスは文字列を保存する name フィールドを持ちます。これを初期化するために、文字列を受け取るコンストラクタが宣言されています。デフォルトのコンストラクタでは、直接 name フィールドを初期化するのではなく、定められた初期値を文字列を初期化するコンストラクタに渡しています。例えば、初期化する文字列が正しいものかどうかを調べる場合などは、オーバーロードされた各コンストラクタで個別に行うのではなく、1 ヶ所に集中させた方が、保守が容易になるのです。
明示的なコンストラクタ起動では、その時点ではインスタンスが生成されていないことに注意しなければなりません。引数リストの文の中に this を含む分を指定することはできないのです。例えば、コード2の代替コンストラクタ起動で this.name を引数に指定した場合はコンパイル・エラーとなります。