オブジェクトの初期化とコンストラクタ
「カプセル化」では、クラスにゲッターとセッターを実装することを学びました。
これによりprivateにしたフィールドでも他のクラスから値を与えることができます。
しかし、もしフィールドがたくさん、例えば100個あったらどうしましょうか?
オブジェクトを1つ作るたびにセッターを100回呼ばなければなりません。
値を設定しなければ自動的にデフォルトの引数が設定されますが、intなど整数型なら0、boolean型ならfalse、オブジェクトならnullなど、意味のあるデータは入りません。
インスタンスを生成したら、多くの場合初期化が必要になります。
いちいちこんなことをやっていては無駄なので、初期化処理を行うメソッドを作り、それを呼び出した方が効率的です。
ポイント!コンストラクタ
Javaのクラスには「コンストラクタ」と呼ばれる特殊なメソッドを定義できます。
これはインスタンスを作成した時点で自動的に実行されるメソッドです。
クラス名と同じ名前で、なおかつ戻り値を持たないメソッドを記述するとコンストラクタとして扱われます。
オブジェクトをどう初期化するかをここにまとめて書くことができます。
ここで、今までと同じようにこのクラスのインスタンスを生成する場合、以下のコードになります。SomeClass obj = new SomeClass( );
これは今まで書いてきたものと同じ書き方ですが、よく見るとコンストラクタへの呼び出しがあります。
一番右のSomeClass( )という部分はメソッドです。
今まで意識しませんでしたが、newの後には必ずコンストラクタを書きます。
これにより、コンストラクタが実行されるのです。
【コンストラクタの利用】
public class Human{ private String name; private int age; private double height; public Human( ){ System.out.println("コンストラクタが呼ばれました!"); } public void setName(String n){ name = n; } public void setAge(int a){ age = a; } public void setHeight(double h){ height = h; } public void selfIntroduce( ){ System.out.println("私の名前は" + name + "です"); System.out.println("私の年齢は" + age + "です"); System.out.println("私の身長は" + height + "です"); } }
public class UseHuman{ public static void main(String args[ ]){ Human taro = new Human( ); taro.name = "太郎"; taro.age = 30; taro.height = 175.5; taro.selfIntroduce( ); } }
実行結果
コンストラクタが呼ばれました! 私の名前は太郎です 私の年齢は30です 私の身長は175.5です
Human.javaにコンストラクタを実装しました。
UseHuman.javaの方に、コンストラクタへの呼び出しが書いてありますので、コンストラクタが動作し「コンストラクタが呼ばれました!」と表示されます。
ポイント!コンストラクタの戻り値
コンストラクタは戻り値を持つことができません。
void型ということですらありません。
voidとも書いてはいけないのです。
ポイント!コンストラクタの呼び出し
コンストラクタは必ずnew演算子にともなって呼び出されます。
従来の
オブジェクト名.メソッド名
という書き方は利用できず、コンパイルエラーになります。
コンストラクタの利用 1
それでは、コンストラクタでデータの初期化をしましょう。
前のプログラムでは呼び出しただけなので、コンストラクタの中でデータの初期化をします。
しかし、下のようなやり方では意味がありません。
public class Human{ private String name; private int age; private double height; public Human( ){ System.out.println("コンストラクタが呼ばれました!"); name = "太郎"; age = 30; height = 175.5; } … }
Humanのオブジェクトをnewするたびにコンストラクタが呼ばれます。
オブジェクトをいくつ作っても同じデータが入ってしまいます。
ではどうしたら良いでしょう。
ちょっと考えてみましょう。
ヒント
- コンストラクタも普通のメソッドと同様に引数を持つことができます。
- コンストラクタも普通のメソッドと同様にオーバーロードできます。
【コンストラクタを利用してデータを初期化する】
public class Human{ private String name; private int age; private double height; public Human( ){ System.out.println("引数なしコンストラクタが呼ばれました!"); } public Human(String n, int a, double h){ System.out.println("引数ありコンストラクタが呼ばれました!"); name = n; age = a; height = h; } public void setName(String n){ name = n; } public void setAge(int a){ age = a; } public void setHeight(double h){ height = h; } public void selfIntroduce( ){ System.out.println("私の名前は" + name + "です"); System.out.println("私の年齢は" + age + "です"); System.out.println("私の身長は" + height + "です"); } }
ポイント!
今までのコンストラクタに加えて、引数を持つコンストラクタを追加しました。
これを呼びたいときはインスタンス化の際に次のように記述します。
Human obj = new Human("太郎", 30, 175.5);
次のプログラムで、どちらのコンストラクタが呼ばれたか確認してみましょう。
【引数ありコンストラクタを利用するプログラム】
public class UseHuman { public static void main(String args[ ]) { Human taro = new Human( ); taro.setName("太郎"); taro.setAge(30); taro.setHeight(175.5); taro.selfIntroduce( ); Human jiro = new Human("次郎", 28, 177.3); jiro.selfIntroduce( ); } }
実行結果
引数なしコンストラクタが呼ばれました! 私の名前は太郎です 私の年齢は30です 私の身長は175.5です 引数ありコンストラクタが呼ばれました! 私の名前は次郎です 私の年齢は28です 私の身長は177.3です
このプログラムではコードが長くなったと感じたことでしょう。
たしかにHuman.javaは長くなりました。
しかし、UseHuman.javaの中では11行目と13行目のたった2行でオブジェクトを生成、初期化、使用できていることに注目してください。
ここで、Humanクラスは部品の機能を定義、UseHumanクラスはそれを使っているアプリケーション本体です。
Humanクラスにたくさんの機能を持たせるのは大変ですが、1度用意してしまえば後から使う側のUseHumanクラスはとても楽になります。
コンストラクタの利用 2
コンストラクタをクラスに記述しなくても初期化をする方法はあります。
クラスのフィールド定義に直接初期化値を書いてしまうという方法です。
ただし、これではすべてのインスタンスに同じ初期化値が設定されます。
このままで十分であることは少ないので、ほとんどの場合はコンストラクタが利用されます。
クラスのフィールドについて、初期化値とコンストラクタでの初期化両方を記述することもできます。
その場合はインスタンス化と同時に初期化値が適用され、その後コンストラクタが呼び出されるので最終的に残っている値はコンストラクタで代入したものが残ります。
【フィールドの初期化】
public class Sample{ private int x = 10; public Sample( ){ x = 20; } public static void main(String[ ] args){ Sample obj = new Sample( ); System.out.println(obj.x); } }
実行結果
20
このプログラムでは、フィールドの値は2回初期化されることになります。
xの値は、まず2行目で、10で初期化されます。
その後、7行目のコンストラクタの呼び出しにより、コンストラクタ内の4行目で20に初期化されます。
new演算子によってインスタンスを生成する際に必ずコンストラクタが呼び出されます。
コンストラクタがオーバーロードされている場合は引数リストが合致するものが自動的に選ばれます。
つまり、複数存在した場合はどれか1つが選択されるのです。
すべてが呼び出されるわけではありません。
では、実行したい処理が複数のコンストラクタに少しずつ書いてあった場合はどうしたら良いでしょうか。
Javaではコンストラクタから他のコンストラクタを呼び出すことができ、「this」というキーワードを用います。
ヒント!
thisとは、その名前の通り、自分自身のインスタンスを意味するキーワードです。
つまり、
this.フィールド名
などと記述すると、それはすなわち、「自分自身のインスタンスのフィールド」を明示することになります。これは、単にフィールド名だけを書いたときと同じなので、わざわざ使うことはあまりありません。
Humanクラスを次のように編集します。
public class Human{ private String name; private int age; private double height; public Human( ){ System.out.println("引数なしコンストラクタが呼ばれました!"); } public Human(String n, int a, double h){ this( ); System.out.println("引数ありコンストラクタが呼ばれました!"); name = n; age = a; height = h; } public void setName(String n){ name = n; } public void setAge(int a){ age = a; } public void setHeight(double h){ height = h; } public void selfIntroduce( ){ System.out.println("私の名前は" + name + "です"); System.out.println("私の年齢は" + age + "です"); System.out.println("私の身長は" + height + "です"); } }
UseHumanクラスを実行します。
実行結果
引数なしコンストラクタが呼ばれました! 私の名前は太郎です 私の年齢は30です 私の身長は175.5です 引数なしコンストラクタが呼ばれました! 引数ありコンストラクタが呼ばれました! 私の名前は次郎です 私の年齢は28です 私の身長は177.3です
今回のプログラムのポイントとなる箇所は11行目にあります。
このthisを使うことで、引数をもつコンストラクタの中から引数を持たないコンストラクタを呼び出しています。
デフォルトコンストラクタ
今回はコンストラクタを利用してきました。
newの次に書いたのがコンストラクタだったわけですが、今まではコンストラクタを記述していませんでした。
存在しないメソッドを呼んでいたのでしょうか?
それは危険なことなのでしょうか?
結論から言うと、これは問題ありません。
クラスにコンストラクタをまったく書かなかった場合は、自動的にコンストラクタが追加されます。
このコンストラクタは引数を持たず、処理も何もしないものです。
これを「デフォルトコンストラクタ」と言います。
ポイント!デフォルトコンストラクタに関する注意
デフォルトコンストラクタは、クラス内に1つでもコンストラクタを記述すると追加されません。
その場合は引数リストの合致したものを呼び出さないとコンパイルエラーになります。
まとめ
オブジェクトの初期化とコンストラクタ
- クラスにはコンストラクタを記述できる。
- コンストラクタとは、初期化用の特殊なメソッドである。
- コンストラクタはクラス名と同じ名前であり、戻り値を持たないものである。
- コンストラクタを作成しなかった場合はデフォルトコンストラクタが自動で用意される。
コンストラクタの利用
- コンストラクタはインスタンス化の時のみ呼ぶことができる。
独学で挫折しそうになったら、オンラインプログラミングスクール