演習問題
- class X{
- X() {System.out.println(“[X]”);}
- void a() { System.out.println(“[x.a]”); }
- void b() { System.out.println(“[x.b]”); }
- }
- class Y extends X {
- Y() { System.out.println(“[Y]”); }
- void a() { System.out.println(“[y.a]”); }
- }
上記のようにクラスX,Yが宣言されている場合、以下のプログラムコードが実行された結果を予測しましょう。
- X x = new X();
- x.a();
- x.b();
- Y y = new Y();
- y.a();
- y.b();
上のプログラムコードですが、クラスXとクラスYには
「X() Y()」
という引数のないコンストラクタが定義されています。
そして処理の内容はそれぞれ「X」「Y」を出力するという内容になっています。
それぞれのなかにメソッドが定義されています。
このプログラムの内容を下の内容で実行します。
もちろんそのままでは実行できないのでメインクラスを設定して実行します。
実行側はまずXクラスをインスタンス化してメソッドを実行します。
Yクラスも同様にインスタンス化してメソッドを実行します。
継承はまず子クラスの中身が優先されます。
親クラスの「X」クラスの中にも「a」というメソッドがありますが、はじめにYクラスの中の「a」というメソッドが優先的に実行されます。そして次に「b」メソッドを実行します。
Yクラスには独自で定義している「b」メソッドはありません。
ですが「Xクラス」を継承しているのでそれを呼び出して実行するという流れになるはずです。
実際にこのような流れになるかどうかプログラムを実行してみたいと思います。
プログラムコード
- class X {
- // Xクラスのコンストラクタ
- X() {
- System . out . println ( “[X]” ) ;
- }
- // Xクラスのaメソッド
- void a() {
- System . out . println ( “[x.a]” ) ;
- }
- // Xクラスのbメソッド
- void b() {
- System . out . println ( “[x.b]” ) ;
- }
- class Y extends X {
- // Yクラスのコンストラクタ
- Y() {
- System . out . println ( “[Y]” ) ;
- }
- // Yクラスのaメソッド
- void a() {
- System . out . println ( “[y.a]” ) ;
- }
- // 本体側のメインクラス
- class Main {
- public static void main(String[] args) {
- X x = new X();
- x.a();
- x.b();
- Y x = new Y();
- y.a();
- y.b();
- }
- }
実行結果
- [X]
- [x.a]
- [x.b]
- [X]
- [Y]
- [y.a]
- [x.b]
解説
親クラス「Xクラス」の挙動
親クラスである「Xクラス」のコンストラクタが定義されました。
このコンストラクタの処理内容は[X]を出力することです。
- X() {
- System . out . println ( “[X]” ) ;
- }
「Xクラス」の中にaメソッドとbメソッドが定義されそれぞれの処理内容が記述されています。
- // Xクラスのaメソッド
- void a() {
- System . out . println ( “[x.a]” ) ;
- }
- // Xクラスのbメソッド
- void b() {
- System . out . println ( “[x.b]” ) ;
- }
子クラス「Yクラス」の挙動
「Yクラス」は親クラスである「Xクラス」を継承するクラスです。extendsを記述します。
- class Y extends X {
コンストラクタとメソッドを定義してそれぞれの処理内容を記述します。
- // Yクラスのコンストラクタ
- Y() {
- System . out . println ( “[Y]” ) ;
- }
- // Yクラスのaメソッド
- void a() {
- System . out . println ( “[y.a]” ) ;
- }
Y() { System.out.println(“[Y]”); }
子クラスのコンストラクタが実行されるときに親クラスのコンストラクタを明示的に呼び出していないので仮想的に
Y() { super();System.out.println(“[Y]”); }
親クラスのコンストラクタが追加されます。
記述していなくても仮想的に追加されるということです。
どういうことかというとまず親クラスのコンストラクタを実行して次に子クラスのコンストラクタの内容を実行するということです。
super();
というのは親クラスのコンストラクタです。
つまりXクラスのコンストラクタの内容の[X]の出力が終わってからやっと「Yクラス」のコンストラクタの内容の実行に移るということです。
「Yクラス」独自のメソッドaですが
void a() { System.out.println(“[y.a]”); }
親クラスも同じメソッドaを持っています。
ですがオーバーライドが発生して、呼び出されてもこちらの方が優先して実行されるということになります。
[y.a] が出力されるということです。
メインクラスの挙動
まず「Xクラス」のインスタンス化がおこなわれます。
このインスタンス化がおこなわれた時点で自動的に「Xクラス」のコンストラクタが実行されます。
- X x = new X();
次に「Xクラス」のaメソッドとbメソッドを呼び出します。
すると「Xクラス」のaメソッドとbメソッドの内容が実行されます。
つまり[x.a]と[x.b]が出力されます。
- x.a();
- x.b();
続いて「Yクラス」のインスタンス化をします。
この時点で自動的に「Yクラス」のコンストラクタが実行されますがその時点で仮想的に「Xクラス」のコンストラクタが実行されます。
つまり一番はじめに「Xクラス」のコンストラクタが実行されるということです。
「Xクラス」のコンストラクタの内容である
[X]
が先に出力されるということになります。
- Y x = new Y();
y.a();
で「Yクラス」のaメソッドを呼び出します。「Xクラス」のaメソッドと名前が同じなためオーバーライドが発生し子クラスのaメソッドが優先して実行されます。
- y.a();
- y.b();
y.b();
「Yクラス」はbメソッドを持っていないため、「Xクラス」のbメソッドの内容が出力されます。
まとめ
この問題のややこしい所は子クラスである「Yクラス」のコンストラクタが実行されるときには自動的に「Xクラス」のコンストラクタも一緒についてくるというところです。
なので[Y]が出力される前に必ず[X]が出力されることを理解してください。
記述されてはいませんが
Y() { super();System.out.println(“[Y]”); }
が隠れていることを理解しましょう。
参考資料
Java 第2版 入門編 ゼロからはじめるプログラミング【電子書籍】[ 三谷純 ]
Java 第3版 入門編 ゼロからはじめるプログラミング (プログラミング学習シリーズ) [ 三谷 純 ]