前置き
JavaSilver11の資格勉強の中で学んだ内容を備忘録として残します。
継承関係にある2つのクラスで同名のフィールドと同名のメソッドがあった時の挙動の理解が難しかったので、検証しながら学習をしてみました。
学習の内容
- フィールドはインスタンス化した時の変数の型によって参照先が変わる。
- メソッドの呼び出しはインスタンス化したクラスによって呼び出し先が変わる。
検証1:インスタンス化=サブクラス、変数の型=スーパークラス
サブクラスのインスタンスを生成し、それをスーパークラス型の変数にセットして呼び出した際の挙動を検証。
検証ソース
class Astudy {
String val = "A";
void print() {
System.out.println("Astudyの出力->" + val);
}
}
class Bstudy extends Astudy {
String val = "B";
void print() {
System.out.println("Bstudyの出力->" + val);
}
}
public class Study {
public static void main(String[] args) {
Astudy a = new Astudy();
Astudy b = new Bstudy();
System.out.println("変数aの出力->" + a.val);
System.out.println("変数bの出力->" + b.val);
a.print();
b.print();
}
}
検証結果
変数aの出力->A // スーパークラスを参照している
変数bの出力->A // スーパークラスを参照している・・・(1)
Astudyの出力->A // スーパークラスのメソッドを呼び出している
Bstudyの出力->B // サブクラスのメソッドを呼び出している・・・(2)
(1)の解説
インスタンス化したのはBstudyクラスだが、出力されたのはAstudyクラスの値。
これはインスタンスを生成したオブジェクトをAstudyクラス型にセットしているため。
フィールド参照は、変数の型で宣言された方を使うというルールがあるため、これに従いAstudyクラスに定義されている値が出力されている。
(2)の解説
メソッドの呼び出しはフィールドと異なりインスタンス化したクラスのメソッドを呼び出すため、Bstudyクラスのprint()メソッドが実行されている。
Astudyクラスを継承しているが、変数にスコープの指定がないためフィールドの参照先はBstudyクラス内に定義されたフィールドの値となる。
// コメント
スコープ=thisやsuperのこと。
thisまたは省略した場合は自身のクラスを参照する。superの場合は継承元のクラスを参照する。
検証2:インスタンス化=サブクラス、変数の型=サブクラス
今度はインスタンス生成するクラスと変数の型を揃えて検証。
検証ソース
class Astudy {
String val = "A";
void print() {
System.out.println("Astudyの出力->" + val);
}
}
class Bstudy extends Astudy {
String val = "B";
void print() {
System.out.println("Bstudyの出力->" + val);
}
}
public class Study {
public static void main(String[] args) {
Astudy a = new Astudy();
Bstudy b = new Bstudy(); // 型をBstudyへ変更
System.out.println("変数aの出力->" + a.val);
System.out.println("変数bの出力->" + b.val);
a.print();
b.print();
}
}
検証結果
変数aの出力->A // スーパークラスを参照している
変数bの出力->B // サブクラスを参照している・・・(1)
Astudyの出力->A // スーパークラスのメソッドを呼び出している
Bstudyの出力->B // サブクラスのメソッドを呼び出している・・・(2)
(1)の解説
インスタンスをサブクラス型の変数にセットしたことで、フィールド参照先が検証1と違う結果になった。
(2)の解説
検証1と同じ結果。
変数の型に影響しないことを確認。
番外編:サブクラスからオーバーライドしたメソッドをなくした時の挙動
検証1、検証2ではBstudyに継承元のAstudyクラスのprint()メソッドをオーバーライドしていたが、これをオーバーライドしなかった場合の挙動を検証。
検証ソース
class Astudy {
String val = "A";
void print() {
System.out.println("Astudyの出力->" + val);
}
}
class Bstudy extends Astudy {
String val = "B";
// print()メソッドを削除
}
public class Study {
public static void main(String[] args) {
Astudy a = new Astudy();
Astudy b = new Bstudy(); // 型をAstudyへ変更
System.out.println("変数aの出力->" + a.val);
System.out.println("変数bの出力->" + b.val);
a.print();
b.print();
}
}
検証結果
変数aの出力->A // スーパークラスを参照している
変数bの出力->A // スーパークラスを参照している・・・(1)
Astudyの出力->A // スーパークラスのメソッドを呼び出している
Astudyの出力->A // スーパークラスのメソッドを呼び出している・・・(2)
(1)の解説
検証1と同じように変数型をスーパークラスにしたため同じ結果となった。
(2)の解説
サブクラスからメソッドがなくなったため、継承元のメソッドが呼び出された。
サブクラスのインスタンスの生成時の動きは、サブクラスとスーパークラスが合わさって1つのインスタンスとなっている。
この時、定義が同じメソッドがそれぞれのクラスに合った場合、サブクラスのメソッドが上書き(オーバーライド)している。
検証1は上記パターンのため、Bstudyクラスのメソッドが呼び出された。
番外編の検証は、上書きされなかったためAstudyクラスのメソッドが呼び出された。
所感
このあたりを意識して実装しなければいけないケースが実務上なかったこともあり、理解がこんがらがっていました。
フィールドが変数の型で宣言された方を使うのに対し、メソッドがインスタンス時のクラスを使うのは、コンパイル時に決まるか実行時に決まるかの違いによるもののようですが、このあたりはまだよく分かっていないです。
今後の課題です。