演習問題
- class A{
- A() { System.out.println(“[A()]”); }
- A(int i) { System.out.println(“[A(int i)]”; }
- }
- class B extends A {
- B() { System.out.println(“[B()]”); }
- B(int i) { System.out.println(“[B(int i)]”); }
- }
- class C extends B { }
上記のようにクラスA,Bが宣言されている場合、以下のプログラムコードが実行された結果を予測しましょう。
- B b0 = new B();
- B b1 = new B(10);
- C c = new C();
演習問題の内容
親クラスであるAには引数のないコンストラクタと引き数ありのコンストラクタが定義されています。
処理の内容は「[A()]」(引数なし)と「[A(int i)]」(引き数あり)を出力するという内容になっています。
つぎに子クラスBは親クラスAを継承します。
親クラスA同様に引数なしのコンストラクタと引き数ありのコンストラクタが定義されます。
最後にクラスZですが、子クラスであるBをさらに継承します。
実行の内容は
B b0 = new B();
で子クラスBをインスタンス化して変数「b0」に代入します。次にもうひとつ
B b1 = new B(10);
は引数を渡してインスタンスを生成します。代入先はBクラスの変数「b1」となります。
最後にBの子クラスであるCクラスのインスタンスを生成して変数「c」に代入します。
これら3行を実行したら何が出力されるのかを予測する問題です。
プログラムコード
- // 親クラスA
- class A {
- // 引数なしコンストラクタ
- A() {
- System . out . println ( “[A()]” ) ;
- }
- // 引数ありコンストラクタ
- A(int i) {
- System . out . println ( “[A(int i)]” ) ;
- }
- }
- // 子クラスB
- classB extends A {
- B() {
- System . out . println ( “[B()]” ) ;
- }
- // B() {
- // super();
- // System . out . println ( “[B()]” ) ;
- // }
- B(int i) {
- System . out . println ( “[B(int i)]” ) ;
- }
- // B(int i) {
- // super();
- // System . out . println ( “[B(int i)]” ) ;
- // }
- }
- // 子クラスBを継承したCクラス
- classC extends B {
- // C( ) {
- // super();
- // }
- public class Main {
- public static void main(String[] args) {
- B b0 = new B();
- B b1 = new B(10);
- C c = new C();
- }
- }
実行結果
- [A()]
- [B()]
- [A()]
- [B(int i)]
- [A()]
- [B()]
解説
親クラス「Aクラス」の挙動
親クラスである「Aクラス」に引数のないコンストラクタが定義されました。
このコンストラクタの処理内容は[A()]を出力することです。
- A() {
- System . out . println ( “[A()]” ) ;
- }
「Aクラス」にもうひとつ引数ありのコンストラクタが定義されました。
引数の型と変数名は( )の中に記述します。
- A(int i) {
- System . out . println ( “[A(int i)]” ) ;
- }
子クラス「Bクラス」の挙動
「Bクラス」は親クラスである「Aクラス」を継承するクラスです。extendsを記述します。
- class B extends A {
引き数なしのコンストラクタと引き数ありのコンストラクタを定義してそれぞれの処理内容を記述します。
- B() {
- System . out . println ( “[B()]” ) ;
- }
- B(int i) {
- System . out . println ( “[B(int i)]” ) ;
- }
子クラスのコンストラクタが実行されるときに親クラスのコンストラクタが仮想的に追加されています。
省略されていますが中身には以下の内容が記述されています。
Bクラスの引数なしのコンストラクタ
- // B( ) {
- // super();
- // System . out . println ( “[B( )]” ) ;
- // }
Bクラスの引数ありのコンストラクタ
- // B(int i) {
- // super();
- // System . out . println ( “[B(int i)]” ) ;
- // }
ここで気を付けなければならないのは「Bクラス」で引数ありでコンストラクタを実行しても最初に呼び出されるのは親クラスの引数のないコンストラクタであるということです。
super();
で呼び出されるのは
A() { System.out.println(“[A()]”); }
だということです。
子クラス「Cクラス」の挙動
「Cクラス」は子クラスであるBクラスを継承しています。
「Cクラス」のコンストラクタは記述されていません。ですが
class C extends B { }
が実行された時点でデフォルトコンストラクタが追加されます。
つまり「Cクラス」の親クラスである「Bクラス」のコンストラクタが実行されます。
ここで一番重要なのが親クラスである「Bクラス」は継承クラスであるため「Bクラス」が呼び出されると「Aクラス」が先に実行されるということです。
つまり「Cクラス」を実行すると最初に[A()]が出力され、次に[B()]が出力されるということです。
メインクラスの挙動
B b0 = new B();
このインスタンス化がおこなわれた時点で自動的に「Aクラス」のコンストラクタが実行されます。
[B()]が出力される前に必ず[A()]が出力されます。
- B b0 = new B();
次に引数をわたしてインスタンスを生成します。
- B b1 = new B(10);
引数を渡しているので実行されるのは引き数ありの方のコンストラクタになります。
- B(int i) {
- System . out . println ( “[B(int i)]” ) ;
- }
そしてこちらも親クラスである「Aクラス」のコンストラクタが先に実行されますが、実行されるのは引数のない方の「Aクラス」のコンストラクタになります。
最後に「Cクラス」をインスタンス化します。
- C c = new C();
「Cクラス」のコンストラクタは存在しません。
ですがインスタンス化された時点でデフォルトコンストラクタが実行されます。
「Cクラス」の親クラスである「Bクラス」のコンストラクタが実行されますが、「Bクラス」は継承クラスであるためその親クラスである「Aクラス」のコンストラクタが先に実行されることになります。
[A()]
[B()]
まとめ
継承関係が複雑になってくるとコンストラクタの動きがややこしくなります。
ですがこれはひとつのポリシーがあって、子クラス(Bクラス)のコンストラクタが実行されるとき常に先に「Aクラス」のコンストラクタが実行されます。
「Bクラス」が子クラスである以上必ず親クラスとペアになっているということを理解してください。
参考資料
Java 第2版 入門編 ゼロからはじめるプログラミング【電子書籍】[ 三谷純 ]
Java 第3版 入門編 ゼロからはじめるプログラミング (プログラミング学習シリーズ) [ 三谷 純 ]